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.listener; 020 021import com.google.inject.Inject; 022import com.plotsquared.bukkit.player.BukkitPlayer; 023import com.plotsquared.bukkit.util.BukkitUtil; 024import com.plotsquared.core.PlotSquared; 025import com.plotsquared.core.configuration.Settings; 026import com.plotsquared.core.configuration.caption.TranslatableCaption; 027import com.plotsquared.core.database.DBFunc; 028import com.plotsquared.core.location.Location; 029import com.plotsquared.core.permissions.Permission; 030import com.plotsquared.core.player.PlotPlayer; 031import com.plotsquared.core.plot.Plot; 032import com.plotsquared.core.plot.PlotArea; 033import com.plotsquared.core.plot.flag.implementations.BlockBurnFlag; 034import com.plotsquared.core.plot.flag.implementations.BlockIgnitionFlag; 035import com.plotsquared.core.plot.flag.implementations.BreakFlag; 036import com.plotsquared.core.plot.flag.implementations.ConcreteHardenFlag; 037import com.plotsquared.core.plot.flag.implementations.CoralDryFlag; 038import com.plotsquared.core.plot.flag.implementations.CropGrowFlag; 039import com.plotsquared.core.plot.flag.implementations.DisablePhysicsFlag; 040import com.plotsquared.core.plot.flag.implementations.DoneFlag; 041import com.plotsquared.core.plot.flag.implementations.ExplosionFlag; 042import com.plotsquared.core.plot.flag.implementations.GrassGrowFlag; 043import com.plotsquared.core.plot.flag.implementations.IceFormFlag; 044import com.plotsquared.core.plot.flag.implementations.IceMeltFlag; 045import com.plotsquared.core.plot.flag.implementations.InstabreakFlag; 046import com.plotsquared.core.plot.flag.implementations.KelpGrowFlag; 047import com.plotsquared.core.plot.flag.implementations.LeafDecayFlag; 048import com.plotsquared.core.plot.flag.implementations.LiquidFlowFlag; 049import com.plotsquared.core.plot.flag.implementations.MycelGrowFlag; 050import com.plotsquared.core.plot.flag.implementations.PlaceFlag; 051import com.plotsquared.core.plot.flag.implementations.RedstoneFlag; 052import com.plotsquared.core.plot.flag.implementations.SnowFormFlag; 053import com.plotsquared.core.plot.flag.implementations.SnowMeltFlag; 054import com.plotsquared.core.plot.flag.implementations.SoilDryFlag; 055import com.plotsquared.core.plot.flag.implementations.VineGrowFlag; 056import com.plotsquared.core.plot.flag.types.BlockTypeWrapper; 057import com.plotsquared.core.plot.flag.types.BooleanFlag; 058import com.plotsquared.core.plot.world.PlotAreaManager; 059import com.plotsquared.core.util.PlotFlagUtil; 060import com.plotsquared.core.util.task.TaskManager; 061import com.plotsquared.core.util.task.TaskTime; 062import com.sk89q.worldedit.WorldEdit; 063import com.sk89q.worldedit.bukkit.BukkitAdapter; 064import com.sk89q.worldedit.world.block.BlockType; 065import net.kyori.adventure.text.Component; 066import net.kyori.adventure.text.minimessage.tag.Tag; 067import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; 068import org.bukkit.Bukkit; 069import org.bukkit.GameMode; 070import org.bukkit.Material; 071import org.bukkit.block.Block; 072import org.bukkit.block.BlockFace; 073import org.bukkit.block.BlockState; 074import org.bukkit.block.data.BlockData; 075import org.bukkit.block.data.type.Dispenser; 076import org.bukkit.block.data.type.Farmland; 077import org.bukkit.entity.Entity; 078import org.bukkit.entity.Fireball; 079import org.bukkit.entity.Player; 080import org.bukkit.entity.Projectile; 081import org.bukkit.event.EventHandler; 082import org.bukkit.event.EventPriority; 083import org.bukkit.event.Listener; 084import org.bukkit.event.block.BlockBreakEvent; 085import org.bukkit.event.block.BlockBurnEvent; 086import org.bukkit.event.block.BlockDamageEvent; 087import org.bukkit.event.block.BlockDispenseEvent; 088import org.bukkit.event.block.BlockExplodeEvent; 089import org.bukkit.event.block.BlockFadeEvent; 090import org.bukkit.event.block.BlockFormEvent; 091import org.bukkit.event.block.BlockFromToEvent; 092import org.bukkit.event.block.BlockGrowEvent; 093import org.bukkit.event.block.BlockIgniteEvent; 094import org.bukkit.event.block.BlockMultiPlaceEvent; 095import org.bukkit.event.block.BlockPhysicsEvent; 096import org.bukkit.event.block.BlockPistonExtendEvent; 097import org.bukkit.event.block.BlockPistonRetractEvent; 098import org.bukkit.event.block.BlockPlaceEvent; 099import org.bukkit.event.block.BlockRedstoneEvent; 100import org.bukkit.event.block.BlockSpreadEvent; 101import org.bukkit.event.block.CauldronLevelChangeEvent; 102import org.bukkit.event.block.EntityBlockFormEvent; 103import org.bukkit.event.block.LeavesDecayEvent; 104import org.bukkit.event.block.MoistureChangeEvent; 105import org.bukkit.event.block.SpongeAbsorbEvent; 106import org.bukkit.event.world.StructureGrowEvent; 107import org.bukkit.projectiles.BlockProjectileSource; 108import org.bukkit.util.Vector; 109import org.checkerframework.checker.nullness.qual.NonNull; 110 111import java.util.Iterator; 112import java.util.List; 113import java.util.Objects; 114import java.util.Set; 115import java.util.UUID; 116import java.util.stream.Collectors; 117import java.util.stream.Stream; 118 119import static org.bukkit.Tag.CORALS; 120import static org.bukkit.Tag.CORAL_BLOCKS; 121import static org.bukkit.Tag.WALL_CORALS; 122 123@SuppressWarnings("unused") 124public class BlockEventListener implements Listener { 125 126 private static final Set<Material> PISTONS = Set.of( 127 Material.PISTON, 128 Material.STICKY_PISTON 129 ); 130 private static final Set<Material> PHYSICS_BLOCKS = Set.of( 131 Material.TURTLE_EGG, 132 Material.TURTLE_SPAWN_EGG 133 ); 134 private static final Set<Material> SNOW = Stream.of(Material.values()) // needed as Tag.SNOW isn't present in 1.16.5 135 .filter(material -> material.name().contains("SNOW")) 136 .filter(Material::isBlock) 137 .collect(Collectors.toUnmodifiableSet()); 138 139 private final PlotAreaManager plotAreaManager; 140 private final WorldEdit worldEdit; 141 142 @Inject 143 public BlockEventListener(final @NonNull PlotAreaManager plotAreaManager, final @NonNull WorldEdit worldEdit) { 144 this.plotAreaManager = plotAreaManager; 145 this.worldEdit = worldEdit; 146 } 147 148 public static void sendBlockChange(final org.bukkit.Location bloc, final BlockData data) { 149 TaskManager.runTaskLater(() -> { 150 String world = bloc.getWorld().getName(); 151 int x = bloc.getBlockX(); 152 int z = bloc.getBlockZ(); 153 int distance = Bukkit.getViewDistance() * 16; 154 155 for (final PlotPlayer<?> player : PlotSquared.platform().playerManager().getPlayers()) { 156 Location location = player.getLocation(); 157 if (location.getWorldName().equals(world)) { 158 if (16 * Math.abs(location.getX() - x) / 16 > distance || 16 * Math.abs(location.getZ() - z) / 16 > distance) { 159 continue; 160 } 161 ((BukkitPlayer) player).player.sendBlockChange(bloc, data); 162 } 163 } 164 }, TaskTime.ticks(3L)); 165 } 166 167 @EventHandler 168 public void onRedstoneEvent(BlockRedstoneEvent event) { 169 Block block = event.getBlock(); 170 Location location = BukkitUtil.adapt(block.getLocation()); 171 PlotArea area = location.getPlotArea(); 172 if (area == null) { 173 return; 174 } 175 Plot plot = location.getOwnedPlot(); 176 if (plot == null) { 177 if (PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, RedstoneFlag.class, false)) { 178 event.setNewCurrent(0); 179 } 180 return; 181 } 182 if (!plot.getFlag(RedstoneFlag.class)) { 183 event.setNewCurrent(0); 184 plot.debug("Redstone event was cancelled because redstone = false"); 185 return; 186 } 187 if (Settings.Redstone.DISABLE_OFFLINE) { 188 boolean disable = false; 189 if (!DBFunc.SERVER.equals(plot.getOwner())) { 190 if (plot.isMerged()) { 191 disable = true; 192 for (UUID owner : plot.getOwners()) { 193 if (PlotSquared.platform().playerManager().getPlayerIfExists(owner) != null) { 194 disable = false; 195 break; 196 } 197 } 198 } else { 199 disable = PlotSquared.platform().playerManager().getPlayerIfExists(plot.getOwnerAbs()) == null; 200 } 201 } 202 if (disable) { 203 for (UUID trusted : plot.getTrusted()) { 204 if (PlotSquared.platform().playerManager().getPlayerIfExists(trusted) != null) { 205 disable = false; 206 break; 207 } 208 } 209 if (disable) { 210 event.setNewCurrent(0); 211 plot.debug("Redstone event was cancelled because no trusted player was in the plot"); 212 return; 213 } 214 } 215 } 216 if (Settings.Redstone.DISABLE_UNOCCUPIED) { 217 for (final PlotPlayer<?> player : PlotSquared.platform().playerManager().getPlayers()) { 218 if (plot.equals(player.getCurrentPlot())) { 219 return; 220 } 221 } 222 event.setNewCurrent(0); 223 } 224 } 225 226 @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) 227 public void onPhysicsEvent(BlockPhysicsEvent event) { 228 Block block = event.getBlock(); 229 Location location = BukkitUtil.adapt(block.getLocation()); 230 PlotArea area = location.getPlotArea(); 231 if (area == null) { 232 return; 233 } 234 Plot plot = area.getOwnedPlotAbs(location); 235 if (plot == null) { 236 return; 237 } 238 if (event.getChangedType().hasGravity() && plot.getFlag(DisablePhysicsFlag.class)) { 239 event.setCancelled(true); 240 sendBlockChange(event.getBlock().getLocation(), event.getBlock().getBlockData()); 241 plot.debug("Prevented block physics and resent block change because disable-physics = true"); 242 return; 243 } 244 if (event.getChangedType() == Material.COMPARATOR) { 245 if (!plot.getFlag(RedstoneFlag.class)) { 246 event.setCancelled(true); 247 plot.debug("Prevented comparator update because redstone = false"); 248 } 249 return; 250 } 251 if (PHYSICS_BLOCKS.contains(event.getChangedType())) { 252 if (plot.getFlag(DisablePhysicsFlag.class)) { 253 event.setCancelled(true); 254 plot.debug("Prevented block physics because disable-physics = true"); 255 } 256 return; 257 } 258 if (Settings.Redstone.DETECT_INVALID_EDGE_PISTONS) { 259 if (PISTONS.contains(block.getType())) { 260 org.bukkit.block.data.Directional piston = (org.bukkit.block.data.Directional) block.getBlockData(); 261 final BlockFace facing = piston.getFacing(); 262 location = location.add(facing.getModX(), facing.getModY(), facing.getModZ()); 263 Plot newPlot = area.getOwnedPlotAbs(location); 264 if (plot.equals(newPlot)) { 265 return; 266 } 267 if (!plot.isMerged() || !plot.getConnectedPlots().contains(newPlot)) { 268 event.setCancelled(true); 269 plot.debug("Prevented piston update because of invalid edge piston detection"); 270 } 271 } 272 } 273 } 274 275 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 276 public void blockCreate(BlockPlaceEvent event) { 277 Location location = BukkitUtil.adapt(event.getBlock().getLocation()); 278 PlotArea area = location.getPlotArea(); 279 if (area == null) { 280 return; 281 } 282 Player player = event.getPlayer(); 283 BukkitPlayer pp = BukkitUtil.adapt(player); 284 Plot plot = area.getPlot(location); 285 if (plot != null) { 286 if (area.notifyIfOutsideBuildArea(pp, location.getY())) { 287 event.setCancelled(true); 288 pp.sendMessage( 289 TranslatableCaption.of("height.height_limit"), 290 TagResolver.builder() 291 .tag("minheight", Tag.inserting(Component.text(area.getMinBuildHeight()))) 292 .tag("maxheight", Tag.inserting(Component.text(area.getMaxBuildHeight()))) 293 .build() 294 ); 295 return; 296 } 297 if (!plot.hasOwner()) { 298 if (!pp.hasPermission(Permission.PERMISSION_ADMIN_BUILD_UNOWNED)) { 299 pp.sendMessage( 300 TranslatableCaption.of("permission.no_permission_event"), 301 TagResolver.resolver( 302 "node", 303 Tag.inserting(Permission.PERMISSION_ADMIN_BUILD_UNOWNED) 304 ) 305 ); 306 event.setCancelled(true); 307 return; 308 } 309 } else if (!plot.isAdded(pp.getUUID())) { 310 List<BlockTypeWrapper> place = plot.getFlag(PlaceFlag.class); 311 if (place != null) { 312 Block block = event.getBlock(); 313 if (place.contains( 314 BlockTypeWrapper.get(BukkitAdapter.asBlockType(block.getType())))) { 315 return; 316 } 317 } 318 if (!pp.hasPermission(Permission.PERMISSION_ADMIN_BUILD_OTHER)) { 319 pp.sendMessage( 320 TranslatableCaption.of("permission.no_permission_event"), 321 TagResolver.resolver( 322 "node", 323 Tag.inserting(Permission.PERMISSION_ADMIN_BUILD_OTHER) 324 ) 325 ); 326 event.setCancelled(true); 327 plot.debug(player.getName() + " could not place " + event.getBlock().getType() 328 + " because of the place = false"); 329 return; 330 } 331 } else if (Settings.Done.RESTRICT_BUILDING && DoneFlag.isDone(plot)) { 332 if (!pp.hasPermission(Permission.PERMISSION_ADMIN_BUILD_OTHER)) { 333 pp.sendMessage( 334 TranslatableCaption.of("done.building_restricted") 335 ); 336 event.setCancelled(true); 337 return; 338 } 339 } 340 if (plot.getFlag(DisablePhysicsFlag.class)) { 341 Block block = event.getBlockPlaced(); 342 if (block.getType().hasGravity()) { 343 sendBlockChange(block.getLocation(), block.getBlockData()); 344 plot.debug(event.getBlock().getType() 345 + " did not fall because of disable-physics = true"); 346 } 347 } 348 } else if (!pp.hasPermission(Permission.PERMISSION_ADMIN_BUILD_ROAD)) { 349 pp.sendMessage( 350 TranslatableCaption.of("permission.no_permission_event"), 351 TagResolver.resolver( 352 "node", 353 Tag.inserting(Permission.PERMISSION_ADMIN_BUILD_ROAD) 354 ) 355 ); 356 event.setCancelled(true); 357 } 358 } 359 360 @EventHandler(priority = EventPriority.LOWEST) 361 public void blockDestroy(BlockBreakEvent event) { 362 Player player = event.getPlayer(); 363 Location location = BukkitUtil.adapt(event.getBlock().getLocation()); 364 PlotArea area = location.getPlotArea(); 365 if (area == null) { 366 return; 367 } 368 Plot plot = area.getPlot(location); 369 if (plot != null) { 370 BukkitPlayer plotPlayer = BukkitUtil.adapt(player); 371 // == rather than <= as we only care about the "ground level" not being destroyed 372 if (event.getBlock().getY() == area.getMinGenHeight()) { 373 if (!plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_DESTROY_GROUNDLEVEL)) { 374 plotPlayer.sendMessage( 375 TranslatableCaption.of("permission.no_permission_event"), 376 TagResolver.resolver( 377 "node", 378 Tag.inserting(Permission.PERMISSION_ADMIN_DESTROY_GROUNDLEVEL) 379 ) 380 ); 381 event.setCancelled(true); 382 return; 383 } 384 } else if (area.notifyIfOutsideBuildArea(plotPlayer, location.getY())) { 385 event.setCancelled(true); 386 plotPlayer.sendMessage( 387 TranslatableCaption.of("height.height_limit"), 388 TagResolver.builder() 389 .tag("minheight", Tag.inserting(Component.text(area.getMinBuildHeight()))) 390 .tag("maxheight", Tag.inserting(Component.text(area.getMaxBuildHeight()))) 391 .build() 392 ); 393 return; 394 } 395 if (!plot.hasOwner()) { 396 if (!plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_DESTROY_UNOWNED, true)) { 397 event.setCancelled(true); 398 } 399 return; 400 } 401 if (!plot.isAdded(plotPlayer.getUUID())) { 402 List<BlockTypeWrapper> destroy = plot.getFlag(BreakFlag.class); 403 Block block = event.getBlock(); 404 final BlockType blockType = BukkitAdapter.asBlockType(block.getType()); 405 for (final BlockTypeWrapper blockTypeWrapper : destroy) { 406 if (blockTypeWrapper.accepts(blockType)) { 407 return; 408 } 409 } 410 if (plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_DESTROY_OTHER)) { 411 return; 412 } 413 plotPlayer.sendMessage( 414 TranslatableCaption.of("permission.no_permission_event"), 415 TagResolver.resolver( 416 "node", 417 Tag.inserting(Permission.PERMISSION_ADMIN_DESTROY_OTHER) 418 ) 419 ); 420 event.setCancelled(true); 421 } else if (Settings.Done.RESTRICT_BUILDING && DoneFlag.isDone(plot)) { 422 if (!plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_BUILD_OTHER)) { 423 plotPlayer.sendMessage( 424 TranslatableCaption.of("done.building_restricted") 425 ); 426 event.setCancelled(true); 427 return; 428 } 429 } 430 return; 431 } 432 BukkitPlayer pp = BukkitUtil.adapt(player); 433 if (pp.hasPermission(Permission.PERMISSION_ADMIN_DESTROY_ROAD)) { 434 return; 435 } 436 if (this.worldEdit != null && pp.getAttribute("worldedit")) { 437 if (player.getInventory().getItemInMainHand().getType() == Material 438 .getMaterial(this.worldEdit.getConfiguration().wandItem)) { 439 return; 440 } 441 } 442 pp.sendMessage( 443 TranslatableCaption.of("permission.no_permission_event"), 444 TagResolver.resolver( 445 "node", 446 Tag.inserting(Permission.PERMISSION_ADMIN_DESTROY_ROAD) 447 ) 448 ); 449 event.setCancelled(true); 450 } 451 452 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 453 public void onBlockSpread(BlockSpreadEvent event) { 454 Block block = event.getBlock(); 455 Location location = BukkitUtil.adapt(block.getLocation()); 456 if (location.isPlotRoad()) { 457 event.setCancelled(true); 458 return; 459 } 460 PlotArea area = location.getPlotArea(); 461 if (area == null) { 462 return; 463 } 464 Plot plot = area.getOwnedPlot(location); 465 if (plot == null) { 466 return; 467 } 468 switch (event.getSource().getType().toString()) { 469 case "GRASS_BLOCK": 470 if (!plot.getFlag(GrassGrowFlag.class)) { 471 plot.debug("Grass could not grow because grass-grow = false"); 472 event.setCancelled(true); 473 } 474 break; 475 case "MYCELIUM": 476 if (!plot.getFlag(MycelGrowFlag.class)) { 477 plot.debug("Mycelium could not grow because mycel-grow = false"); 478 event.setCancelled(true); 479 } 480 break; 481 case "WEEPING_VINES": 482 case "TWISTING_VINES": 483 case "CAVE_VINES": 484 case "VINE": 485 case "GLOW_BERRIES": 486 if (!plot.getFlag(VineGrowFlag.class)) { 487 plot.debug("Vine could not grow because vine-grow = false"); 488 event.setCancelled(true); 489 } 490 break; 491 case "KELP": 492 if (!plot.getFlag(KelpGrowFlag.class)) { 493 plot.debug("Kelp could not grow because kelp-grow = false"); 494 event.setCancelled(true); 495 } 496 case "BUDDING_AMETHYST": 497 if (!plot.getFlag(CropGrowFlag.class)) { 498 plot.debug("Amethyst clusters could not grow because crop-grow = false"); 499 event.setCancelled(true); 500 } 501 break; 502 } 503 } 504 505 @EventHandler(priority = EventPriority.HIGHEST) 506 public void onCauldronEmpty(CauldronLevelChangeEvent event) { 507 Entity entity = event.getEntity(); 508 Location location = BukkitUtil.adapt(event.getBlock().getLocation()); 509 PlotArea area = location.getPlotArea(); 510 if (area == null) { 511 return; 512 } 513 Plot plot = area.getPlot(location); 514 // TODO Add flags for specific control over cauldron changes (rain, dripstone...) 515 switch (event.getReason()) { 516 case BANNER_WASH, ARMOR_WASH, EXTINGUISH -> { 517 if (entity instanceof Player player) { 518 BukkitPlayer plotPlayer = BukkitUtil.adapt(player); 519 if (plot != null) { 520 if (!plot.hasOwner()) { 521 if (plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_INTERACT_UNOWNED)) { 522 return; 523 } 524 } else if (!plot.isAdded(plotPlayer.getUUID())) { 525 if (plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_INTERACT_OTHER)) { 526 return; 527 } 528 } else { 529 return; 530 } 531 } else { 532 if (plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_INTERACT_ROAD)) { 533 return; 534 } 535 if (this.worldEdit != null && plotPlayer.getAttribute("worldedit")) { 536 if (player.getInventory().getItemInMainHand().getType() == Material 537 .getMaterial(this.worldEdit.getConfiguration().wandItem)) { 538 return; 539 } 540 } 541 } 542 } 543 if (event.getReason() == CauldronLevelChangeEvent.ChangeReason.EXTINGUISH && event.getEntity() != null) { 544 event.getEntity().setFireTicks(0); 545 } 546 // Though the players fire ticks are modified, 547 // the cauldron water level change is cancelled and the event should represent that. 548 event.setCancelled(true); 549 } 550 default -> { 551 // Bucket empty, Bucket fill, Bottle empty, Bottle fill are already handled in PlayerInteract event 552 // Evaporation or Unknown reasons do not need to be cancelled as they are considered natural causes 553 } 554 } 555 } 556 557 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 558 public void onBlockForm(BlockFormEvent event) { 559 if (event instanceof EntityBlockFormEvent) { 560 return; // handled below 561 } 562 Block block = event.getBlock(); 563 Location location = BukkitUtil.adapt(block.getLocation()); 564 if (location.isPlotRoad()) { 565 event.setCancelled(true); 566 return; 567 } 568 PlotArea area = location.getPlotArea(); 569 if (area == null) { 570 return; 571 } 572 Plot plot = area.getOwnedPlot(location); 573 if (plot == null) { 574 return; 575 } 576 if (!area.buildRangeContainsY(location.getY())) { 577 event.setCancelled(true); 578 return; 579 } 580 if (org.bukkit.Tag.SNOW.isTagged(event.getNewState().getType())) { 581 if (!plot.getFlag(SnowFormFlag.class)) { 582 plot.debug("Snow could not form because snow-form = false"); 583 event.setCancelled(true); 584 } 585 return; 586 } 587 if (org.bukkit.Tag.ICE.isTagged(event.getNewState().getType())) { 588 if (!plot.getFlag(IceFormFlag.class)) { 589 plot.debug("Ice could not form because ice-form = false"); 590 event.setCancelled(true); 591 } 592 } 593 if (event.getNewState().getType().toString().endsWith("CONCRETE")) { 594 if (!plot.getFlag(ConcreteHardenFlag.class)) { 595 plot.debug("Concrete powder could not harden because concrete-harden = false"); 596 event.setCancelled(true); 597 } 598 } 599 } 600 601 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 602 public void onEntityBlockForm(EntityBlockFormEvent event) { 603 String world = event.getBlock().getWorld().getName(); 604 if (!this.plotAreaManager.hasPlotArea(world)) { 605 return; 606 } 607 Location location = BukkitUtil.adapt(event.getBlock().getLocation()); 608 PlotArea area = location.getPlotArea(); 609 if (area == null) { 610 return; 611 } 612 Plot plot = area.getOwnedPlot(location); 613 if (plot == null) { 614 event.setCancelled(true); 615 return; 616 } 617 Class<? extends BooleanFlag<?>> flag; 618 if (org.bukkit.Tag.SNOW.isTagged(event.getNewState().getType())) { 619 flag = SnowFormFlag.class; 620 } else if (org.bukkit.Tag.ICE.isTagged(event.getNewState().getType())) { 621 flag = IceFormFlag.class; 622 } else { 623 return; 624 } 625 boolean allowed = plot.getFlag(flag); 626 Entity entity = event.getEntity(); 627 if (entity instanceof Player player) { 628 BukkitPlayer plotPlayer = BukkitUtil.adapt(player); 629 if (!plot.isAdded(plotPlayer.getUUID())) { 630 if (allowed) { 631 return; // player is not added but forming <flag> is allowed 632 } 633 plot.debug(String.format( 634 "%s could not be formed because %s = false (entity is player)", 635 event.getNewState().getType(), 636 flag == SnowFormFlag.class ? "snow-form" : "ice-form" 637 )); 638 event.setCancelled(true); // player is not added and forming <flag> isn't allowed 639 } 640 return; // event is cancelled if not added and not allowed, otherwise forming <flag> is allowed 641 } 642 if (plot.hasOwner()) { 643 if (allowed) { 644 return; 645 } 646 plot.debug(String.format( 647 "%s could not be formed because %s = false (entity is not player)", 648 event.getNewState().getType(), 649 flag == SnowFormFlag.class ? "snow-form" : "ice-form" 650 )); 651 event.setCancelled(true); 652 } 653 } 654 655 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 656 public void onBlockDamage(BlockDamageEvent event) { 657 Player player = event.getPlayer(); 658 Location location = BukkitUtil.adapt(event.getBlock().getLocation()); 659 PlotArea area = location.getPlotArea(); 660 if (area == null) { 661 return; 662 } 663 if (player.getGameMode() != GameMode.SURVIVAL) { 664 return; 665 } 666 Plot plot = area.getPlot(location); 667 if (plot != null) { 668 if (plot.getFlag(InstabreakFlag.class)) { 669 Block block = event.getBlock(); 670 BlockBreakEvent call = new BlockBreakEvent(block, player); 671 Bukkit.getServer().getPluginManager().callEvent(call); 672 if (!call.isCancelled()) { 673 if (Settings.Flags.INSTABREAK_CONSIDER_TOOL) { 674 block.breakNaturally(event.getItemInHand()); 675 } else { 676 block.breakNaturally(); 677 } 678 } 679 } 680 // == rather than <= as we only care about the "ground level" not being destroyed 681 if (location.getY() == area.getMinGenHeight()) { 682 event.setCancelled(true); 683 return; 684 } 685 if (!plot.hasOwner()) { 686 BukkitPlayer plotPlayer = BukkitUtil.adapt(player); 687 if (plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_DESTROY_UNOWNED)) { 688 return; 689 } 690 event.setCancelled(true); 691 return; 692 } 693 BukkitPlayer plotPlayer = BukkitUtil.adapt(player); 694 if (!plot.isAdded(plotPlayer.getUUID())) { 695 List<BlockTypeWrapper> destroy = plot.getFlag(BreakFlag.class); 696 Block block = event.getBlock(); 697 if (destroy 698 .contains(BlockTypeWrapper.get(BukkitAdapter.asBlockType(block.getType()))) 699 || plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_DESTROY_OTHER)) { 700 return; 701 } 702 plot.debug(player.getName() + " could not break " + block.getType() 703 + " because it was not in the break flag"); 704 event.setCancelled(true); 705 return; 706 } 707 return; 708 } 709 BukkitPlayer plotPlayer = BukkitUtil.adapt(player); 710 if (plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_DESTROY_ROAD)) { 711 return; 712 } 713 event.setCancelled(true); 714 } 715 716 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 717 public void onFade(BlockFadeEvent event) { 718 Block block = event.getBlock(); 719 Location location = BukkitUtil.adapt(block.getLocation()); 720 PlotArea area = location.getPlotArea(); 721 if (area == null) { 722 return; 723 } 724 Plot plot = area.getOwnedPlot(location); 725 if (plot == null) { 726 event.setCancelled(true); 727 return; 728 } 729 Material blockType = block.getType(); 730 if (org.bukkit.Tag.ICE.isTagged(blockType)) { 731 if (!plot.getFlag(IceMeltFlag.class)) { 732 plot.debug("Ice could not melt because ice-melt = false"); 733 event.setCancelled(true); 734 } 735 return; 736 } 737 if (org.bukkit.Tag.SNOW.isTagged(blockType)) { 738 if (!plot.getFlag(SnowMeltFlag.class)) { 739 plot.debug("Snow could not melt because snow-melt = false"); 740 event.setCancelled(true); 741 } 742 return; 743 } 744 if (blockType == Material.FARMLAND) { 745 if (!plot.getFlag(SoilDryFlag.class)) { 746 plot.debug("Soil could not dry because soil-dry = false"); 747 event.setCancelled(true); 748 } 749 return; 750 } 751 if (CORAL_BLOCKS.isTagged(blockType) || CORALS.isTagged(blockType) || WALL_CORALS.isTagged(blockType)) { 752 if (!plot.getFlag(CoralDryFlag.class)) { 753 plot.debug("Coral could not dry because coral-dry = false"); 754 event.setCancelled(true); 755 } 756 } 757 } 758 759 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 760 public void onMoistureChange(MoistureChangeEvent event) { 761 Block block = event.getBlock(); 762 Location location = BukkitUtil.adapt(block.getLocation()); 763 PlotArea area = location.getPlotArea(); 764 765 if (area == null) { 766 return; 767 } 768 769 Plot plot = area.getOwnedPlot(location); 770 771 if (plot == null) { 772 event.setCancelled(true); 773 return; 774 } 775 776 if (block.getBlockData() instanceof Farmland farmland && event 777 .getNewState() 778 .getBlockData() instanceof Farmland newFarmland) { 779 int currentMoisture = farmland.getMoisture(); 780 int newMoisture = newFarmland.getMoisture(); 781 782 // farmland gets moisturizes 783 if (newMoisture > currentMoisture) { 784 return; 785 } 786 787 if (plot.getFlag(SoilDryFlag.class)) { 788 return; 789 } 790 791 plot.debug("Soil could not dry because soil-dry = false"); 792 event.setCancelled(true); 793 } 794 } 795 796 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 797 public void onChange(BlockFromToEvent event) { 798 Block fromBlock = event.getBlock(); 799 800 // Check liquid flow flag inside of origin plot too 801 final Location fromLocation = BukkitUtil.adapt(fromBlock.getLocation()); 802 final PlotArea fromArea = fromLocation.getPlotArea(); 803 if (fromArea != null) { 804 final Plot fromPlot = fromArea.getOwnedPlot(fromLocation); 805 if (fromPlot != null && fromPlot.getFlag(LiquidFlowFlag.class) == LiquidFlowFlag.FlowStatus.DISABLED && event 806 .getBlock() 807 .isLiquid()) { 808 fromPlot.debug("Liquid could not flow because liquid-flow = disabled"); 809 event.setCancelled(true); 810 return; 811 } 812 } 813 814 Block toBlock = event.getToBlock(); 815 Location toLocation = BukkitUtil.adapt(toBlock.getLocation()); 816 PlotArea toArea = toLocation.getPlotArea(); 817 if (toArea == null) { 818 if (fromBlock.getType() == Material.DRAGON_EGG && fromArea != null) { 819 event.setCancelled(true); 820 } 821 return; 822 } 823 if (!toArea.buildRangeContainsY(toLocation.getY())) { 824 event.setCancelled(true); 825 return; 826 } 827 Plot toPlot = toArea.getOwnedPlot(toLocation); 828 829 if (fromBlock.getType() == Material.DRAGON_EGG && fromArea != null) { 830 final Plot fromPlot = fromArea.getOwnedPlot(fromLocation); 831 832 if (fromPlot != null || toPlot != null) { 833 if ((fromPlot == null || !fromPlot.equals(toPlot)) && (toPlot == null || !toPlot.equals(fromPlot))) { 834 event.setCancelled(true); 835 return; 836 } 837 } 838 } 839 840 if (toPlot != null) { 841 if (!toArea.contains(fromLocation.getX(), fromLocation.getZ()) || !Objects.equals( 842 toPlot, 843 toArea.getOwnedPlot(fromLocation) 844 )) { 845 event.setCancelled(true); 846 return; 847 } 848 if (toPlot.getFlag(LiquidFlowFlag.class) == LiquidFlowFlag.FlowStatus.ENABLED && event.getBlock().isLiquid()) { 849 return; 850 } 851 if (toPlot.getFlag(DisablePhysicsFlag.class)) { 852 toPlot.debug(event.getBlock().getType() + " could not update because disable-physics = true"); 853 event.setCancelled(true); 854 return; 855 } 856 if (toPlot.getFlag(LiquidFlowFlag.class) == LiquidFlowFlag.FlowStatus.DISABLED && event.getBlock().isLiquid()) { 857 toPlot.debug("Liquid could not flow because liquid-flow = disabled"); 858 event.setCancelled(true); 859 } 860 } else if (!toArea.contains(fromLocation.getX(), fromLocation.getZ()) || !Objects.equals( 861 null, 862 toArea.getOwnedPlot(fromLocation) 863 )) { 864 event.setCancelled(true); 865 } else if (event.getBlock().isLiquid()) { 866 final org.bukkit.Location location = event.getBlock().getLocation(); 867 868 /* 869 X = block location 870 A-H = potential plot locations 871 Z 872 ^ 873 | A B C 874 o D X E 875 | F G H 876 v 877 <-----O-----> x 878 */ 879 if (BukkitUtil.adapt(location.clone().add(-1, 0, 1) /* A */).getPlot() != null 880 || BukkitUtil.adapt(location.clone().add(1, 0, 0) /* B */).getPlot() != null 881 || BukkitUtil.adapt(location.clone().add(1, 0, 1) /* C */).getPlot() != null 882 || BukkitUtil.adapt(location.clone().add(-1, 0, 0) /* D */).getPlot() != null 883 || BukkitUtil.adapt(location.clone().add(1, 0, 0) /* E */).getPlot() != null 884 || BukkitUtil.adapt(location.clone().add(-1, 0, -1) /* F */).getPlot() != null 885 || BukkitUtil.adapt(location.clone().add(0, 0, -1) /* G */).getPlot() != null 886 || BukkitUtil.adapt(location.clone().add(1, 0, 1) /* H */).getPlot() != null) { 887 event.setCancelled(true); 888 } 889 } 890 } 891 892 893 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 894 public void onGrow(BlockGrowEvent event) { 895 Block block = event.getBlock(); 896 Location location = BukkitUtil.adapt(block.getLocation()); 897 898 PlotArea area = location.getPlotArea(); 899 if (area == null) { 900 return; 901 } 902 903 if (!area.buildRangeContainsY(location.getY())) { 904 event.setCancelled(true); 905 return; 906 } 907 908 Plot plot = location.getOwnedPlot(); 909 if (plot == null || !plot.getFlag(CropGrowFlag.class)) { 910 if (plot != null) { 911 plot.debug("Crop grow event was cancelled because crop-grow = false"); 912 } 913 event.setCancelled(true); 914 } 915 } 916 917 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 918 public void onBlockPistonExtend(BlockPistonExtendEvent event) { 919 Block block = event.getBlock(); 920 Location location = BukkitUtil.adapt(block.getLocation()); 921 BlockFace face = event.getDirection(); 922 Vector relative = new Vector(face.getModX(), face.getModY(), face.getModZ()); 923 PlotArea area = location.getPlotArea(); 924 if (area == null) { 925 if (!this.plotAreaManager.hasPlotArea(location.getWorldName())) { 926 return; 927 } 928 for (Block block1 : event.getBlocks()) { 929 Location bloc = BukkitUtil.adapt(block1.getLocation()); 930 if (bloc.isPlotArea() || bloc 931 .add(relative.getBlockX(), relative.getBlockY(), relative.getBlockZ()) 932 .isPlotArea()) { 933 event.setCancelled(true); 934 return; 935 } 936 } 937 if (location.add(relative.getBlockX(), relative.getBlockY(), relative.getBlockZ()).isPlotArea()) { 938 // Prevent pistons from extending if they are: bordering a plot 939 // area, facing inside plot area, and not pushing any blocks 940 event.setCancelled(true); 941 } 942 return; 943 } 944 Plot plot = area.getOwnedPlot(location); 945 if (plot == null) { 946 event.setCancelled(true); 947 return; 948 } 949 for (Block block1 : event.getBlocks()) { 950 Location bloc = BukkitUtil.adapt(block1.getLocation()); 951 Location newLoc = bloc.add(relative.getBlockX(), relative.getBlockY(), relative.getBlockZ()); 952 if (!area.contains(bloc.getX(), bloc.getZ()) || !area.contains(newLoc)) { 953 event.setCancelled(true); 954 return; 955 } 956 if (!plot.equals(area.getOwnedPlot(bloc)) || !plot.equals(area.getOwnedPlot(newLoc))) { 957 event.setCancelled(true); 958 return; 959 } 960 if (!area.buildRangeContainsY(bloc.getY()) || !area.buildRangeContainsY(newLoc.getY())) { 961 event.setCancelled(true); 962 return; 963 } 964 } 965 if (!plot.equals(area.getOwnedPlot(location.add(relative.getBlockX(), relative.getBlockY(), relative.getBlockZ())))) { 966 // This branch is only necessary to prevent pistons from extending 967 // if they are: on a plot edge, facing outside the plot, and not 968 // pushing any blocks 969 event.setCancelled(true); 970 } 971 } 972 973 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 974 public void onBlockPistonRetract(BlockPistonRetractEvent event) { 975 Block block = event.getBlock(); 976 Location location = BukkitUtil.adapt(block.getLocation()); 977 BlockFace face = event.getDirection(); 978 Vector relative = new Vector(face.getModX(), face.getModY(), face.getModZ()); 979 PlotArea area = location.getPlotArea(); 980 if (area == null) { 981 if (!this.plotAreaManager.hasPlotArea(location.getWorldName())) { 982 return; 983 } 984 for (Block block1 : event.getBlocks()) { 985 Location bloc = BukkitUtil.adapt(block1.getLocation()); 986 Location newLoc = bloc.add(relative.getBlockX(), relative.getBlockY(), relative.getBlockZ()); 987 if (bloc.isPlotArea() || newLoc.isPlotArea()) { 988 event.setCancelled(true); 989 return; 990 } 991 } 992 return; 993 } 994 Plot plot = area.getOwnedPlot(location); 995 if (plot == null) { 996 event.setCancelled(true); 997 return; 998 } 999 for (Block block1 : event.getBlocks()) { 1000 Location bloc = BukkitUtil.adapt(block1.getLocation()); 1001 Location newLoc = bloc.add(relative.getBlockX(), relative.getBlockY(), relative.getBlockZ()); 1002 if (!area.contains(bloc.getX(), bloc.getZ()) || !area.contains(newLoc)) { 1003 event.setCancelled(true); 1004 return; 1005 } 1006 if (!plot.equals(area.getOwnedPlot(bloc)) || !plot.equals(area.getOwnedPlot(newLoc))) { 1007 event.setCancelled(true); 1008 return; 1009 } 1010 if (!area.buildRangeContainsY(bloc.getY()) || !area.buildRangeContainsY(newLoc.getY())) { 1011 event.setCancelled(true); 1012 return; 1013 } 1014 } 1015 } 1016 1017 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 1018 public void onBlockDispense(BlockDispenseEvent event) { 1019 if (!this.plotAreaManager.hasPlotArea(event.getBlock().getWorld().getName())) { 1020 return; 1021 } 1022 Material type = event.getItem().getType(); 1023 switch (type.toString()) { 1024 case "SHULKER_BOX", "WHITE_SHULKER_BOX", "ORANGE_SHULKER_BOX", "MAGENTA_SHULKER_BOX", "LIGHT_BLUE_SHULKER_BOX", 1025 "YELLOW_SHULKER_BOX", "LIME_SHULKER_BOX", "PINK_SHULKER_BOX", "GRAY_SHULKER_BOX", "LIGHT_GRAY_SHULKER_BOX", 1026 "CYAN_SHULKER_BOX", "PURPLE_SHULKER_BOX", "BLUE_SHULKER_BOX", "BROWN_SHULKER_BOX", "GREEN_SHULKER_BOX", 1027 "RED_SHULKER_BOX", "BLACK_SHULKER_BOX", "CARVED_PUMPKIN", "WITHER_SKELETON_SKULL", "FLINT_AND_STEEL", 1028 "BONE_MEAL", "SHEARS", "GLASS_BOTTLE", "GLOWSTONE", "COD_BUCKET", "PUFFERFISH_BUCKET", "SALMON_BUCKET", 1029 "TROPICAL_FISH_BUCKET", "AXOLOTL_BUCKET", "BUCKET", "WATER_BUCKET", "LAVA_BUCKET", "TADPOLE_BUCKET" -> { 1030 if (event.getBlock().getType() == Material.DROPPER) { 1031 return; 1032 } 1033 BlockFace targetFace = ((Dispenser) event.getBlock().getBlockData()).getFacing(); 1034 Location location = BukkitUtil.adapt(event.getBlock().getRelative(targetFace).getLocation()); 1035 if (location.isPlotRoad()) { 1036 event.setCancelled(true); 1037 return; 1038 } 1039 PlotArea area = location.getPlotArea(); 1040 if (area != null && !area.buildRangeContainsY(location.getY())) { 1041 event.setCancelled(true); 1042 } 1043 } 1044 } 1045 } 1046 1047 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 1048 public void onStructureGrow(StructureGrowEvent event) { 1049 if (!this.plotAreaManager.hasPlotArea(event.getWorld().getName())) { 1050 return; 1051 } 1052 List<org.bukkit.block.BlockState> blocks = event.getBlocks(); 1053 if (blocks.isEmpty()) { 1054 return; 1055 } 1056 Location location = BukkitUtil.adapt(blocks.get(0).getLocation()); 1057 PlotArea area = location.getPlotArea(); 1058 if (area == null) { 1059 for (int i = blocks.size() - 1; i >= 0; i--) { 1060 location = BukkitUtil.adapt(blocks.get(i).getLocation()); 1061 if (location.isPlotArea()) { 1062 blocks.remove(i); 1063 } 1064 } 1065 return; 1066 } else { 1067 Plot origin = area.getOwnedPlot(location); 1068 if (origin == null) { 1069 event.setCancelled(true); 1070 return; 1071 } 1072 for (int i = blocks.size() - 1; i >= 0; i--) { 1073 location = BukkitUtil.adapt(blocks.get(i).getLocation()); 1074 if (!area.contains(location.getX(), location.getZ())) { 1075 blocks.remove(i); 1076 continue; 1077 } 1078 Plot plot = area.getOwnedPlot(location); 1079 if (!Objects.equals(plot, origin)) { 1080 event.getBlocks().remove(i); 1081 continue; 1082 } 1083 if (!area.buildRangeContainsY(location.getY())) { 1084 event.getBlocks().remove(i); 1085 } 1086 } 1087 } 1088 Plot origin = area.getPlot(location); 1089 if (origin == null) { 1090 event.setCancelled(true); 1091 return; 1092 } 1093 for (int i = blocks.size() - 1; i >= 0; i--) { 1094 location = BukkitUtil.adapt(blocks.get(i).getLocation()); 1095 Plot plot = area.getOwnedPlot(location); 1096 /* 1097 * plot → the base plot of the merged area 1098 * origin → the plot where the event gets called 1099 */ 1100 1101 // Are plot and origin different AND are both plots merged 1102 if (!Objects.equals(plot, origin) && (!plot.isMerged() && !origin.isMerged())) { 1103 event.getBlocks().remove(i); 1104 } 1105 } 1106 } 1107 1108 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 1109 public void onBigBoom(BlockExplodeEvent event) { 1110 Block block = event.getBlock(); 1111 Location location = BukkitUtil.adapt(block.getLocation()); 1112 String world = location.getWorldName(); 1113 if (!this.plotAreaManager.hasPlotArea(world)) { 1114 return; 1115 } 1116 PlotArea area = location.getPlotArea(); 1117 if (area == null) { 1118 Iterator<Block> iterator = event.blockList().iterator(); 1119 while (iterator.hasNext()) { 1120 location = BukkitUtil.adapt(iterator.next().getLocation()); 1121 if (location.isPlotArea()) { 1122 iterator.remove(); 1123 } 1124 } 1125 return; 1126 } 1127 Plot plot = area.getOwnedPlot(location); 1128 if (plot == null || !plot.getFlag(ExplosionFlag.class)) { 1129 event.setCancelled(true); 1130 if (plot != null) { 1131 plot.debug("Explosion was cancelled because explosion = false"); 1132 } 1133 return; 1134 } 1135 event.blockList().removeIf(blox -> !plot.equals(area.getOwnedPlot(BukkitUtil.adapt(blox.getLocation())))); 1136 } 1137 1138 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 1139 public void onBlockBurn(BlockBurnEvent event) { 1140 Block block = event.getBlock(); 1141 Location location = BukkitUtil.adapt(block.getLocation()); 1142 1143 PlotArea area = location.getPlotArea(); 1144 if (area == null) { 1145 return; 1146 } 1147 1148 Plot plot = location.getOwnedPlot(); 1149 if (plot == null || !plot.getFlag(BlockBurnFlag.class)) { 1150 if (plot != null) { 1151 plot.debug("Block burning was cancelled because block-burn = false"); 1152 } 1153 event.setCancelled(true); 1154 } 1155 1156 } 1157 1158 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 1159 public void onBlockIgnite(BlockIgniteEvent event) { 1160 Player player = event.getPlayer(); 1161 Entity ignitingEntity = event.getIgnitingEntity(); 1162 Block block = event.getBlock(); 1163 BlockIgniteEvent.IgniteCause igniteCause = event.getCause(); 1164 Location location1 = BukkitUtil.adapt(block.getLocation()); 1165 PlotArea area = location1.getPlotArea(); 1166 if (area == null) { 1167 return; 1168 } 1169 if (igniteCause == BlockIgniteEvent.IgniteCause.LIGHTNING) { 1170 event.setCancelled(true); 1171 return; 1172 } 1173 1174 Plot plot = area.getOwnedPlot(location1); 1175 if (player != null) { 1176 BukkitPlayer pp = BukkitUtil.adapt(player); 1177 if (area.notifyIfOutsideBuildArea(pp, location1.getY())) { 1178 event.setCancelled(true); 1179 return; 1180 } 1181 if (plot == null) { 1182 if (!PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, BlockIgnitionFlag.class, true) && !pp.hasPermission( 1183 Permission.PERMISSION_ADMIN_BUILD_ROAD 1184 )) { 1185 pp.sendMessage( 1186 TranslatableCaption.of("permission.no_permission_event"), 1187 TagResolver.resolver( 1188 "node", 1189 Tag.inserting(Permission.PERMISSION_ADMIN_BUILD_ROAD) 1190 ) 1191 ); 1192 event.setCancelled(true); 1193 } 1194 } else if (!plot.hasOwner()) { 1195 if (!PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, BlockIgnitionFlag.class, true) && !pp.hasPermission( 1196 Permission.PERMISSION_ADMIN_BUILD_UNOWNED 1197 )) { 1198 pp.sendMessage( 1199 TranslatableCaption.of("permission.no_permission_event"), 1200 TagResolver.resolver( 1201 "node", 1202 Tag.inserting(Permission.PERMISSION_ADMIN_BUILD_UNOWNED) 1203 ) 1204 ); 1205 event.setCancelled(true); 1206 } 1207 } else if (!plot.isAdded(pp.getUUID())) { 1208 if (!pp.hasPermission(Permission.PERMISSION_ADMIN_BUILD_OTHER)) { 1209 pp.sendMessage( 1210 TranslatableCaption.of("permission.no_permission_event"), 1211 TagResolver.resolver( 1212 "node", 1213 Tag.inserting(Permission.PERMISSION_ADMIN_BUILD_OTHER) 1214 ) 1215 ); 1216 event.setCancelled(true); 1217 } 1218 } else if (!plot.getFlag(BlockIgnitionFlag.class)) { 1219 event.setCancelled(true); 1220 plot.debug("Block ignition was cancelled because block-ignition = false"); 1221 } 1222 } else { 1223 if (plot == null) { 1224 event.setCancelled(true); 1225 return; 1226 } 1227 if (ignitingEntity != null) { 1228 if (!plot.getFlag(BlockIgnitionFlag.class)) { 1229 event.setCancelled(true); 1230 plot.debug("Block ignition was cancelled because block-ignition = false"); 1231 return; 1232 } 1233 if (igniteCause == BlockIgniteEvent.IgniteCause.FIREBALL) { 1234 if (ignitingEntity instanceof Fireball) { 1235 Projectile fireball = (Projectile) ignitingEntity; 1236 Location location = null; 1237 if (fireball.getShooter() instanceof Entity shooter) { 1238 location = BukkitUtil.adapt(shooter.getLocation()); 1239 } else if (fireball.getShooter() instanceof BlockProjectileSource) { 1240 Block shooter = 1241 ((BlockProjectileSource) fireball.getShooter()).getBlock(); 1242 location = BukkitUtil.adapt(shooter.getLocation()); 1243 } 1244 if (location != null && !plot.equals(location.getPlot())) { 1245 event.setCancelled(true); 1246 } 1247 } 1248 } 1249 1250 } else if (event.getIgnitingBlock() != null) { 1251 Block ignitingBlock = event.getIgnitingBlock(); 1252 Plot plotIgnited = BukkitUtil.adapt(ignitingBlock.getLocation()).getPlot(); 1253 if (igniteCause == BlockIgniteEvent.IgniteCause.FLINT_AND_STEEL && ( 1254 !plot.getFlag(BlockIgnitionFlag.class) || plotIgnited == null || !plotIgnited 1255 .equals(plot)) || (igniteCause == BlockIgniteEvent.IgniteCause.SPREAD 1256 || igniteCause == BlockIgniteEvent.IgniteCause.LAVA) && ( 1257 !plot.getFlag(BlockIgnitionFlag.class) || plotIgnited == null || !plotIgnited 1258 .equals(plot))) { 1259 event.setCancelled(true); 1260 } 1261 } 1262 } 1263 } 1264 1265 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 1266 public void onLeavesDecay(LeavesDecayEvent event) { 1267 Block block = event.getBlock(); 1268 Location location = BukkitUtil.adapt(block.getLocation()); 1269 1270 PlotArea area = location.getPlotArea(); 1271 if (area == null) { 1272 return; 1273 } 1274 1275 Plot plot = location.getOwnedPlot(); 1276 if (plot == null || !plot.getFlag(LeafDecayFlag.class)) { 1277 if (plot != null) { 1278 plot.debug("Leaf decaying was cancelled because leaf-decay = false"); 1279 } 1280 event.setCancelled(true); 1281 } 1282 1283 } 1284 1285 @EventHandler(ignoreCancelled = true) 1286 public void onSpongeAbsorb(SpongeAbsorbEvent event) { 1287 Block sponge = event.getBlock(); 1288 Location location = BukkitUtil.adapt(sponge.getLocation()); 1289 PlotArea area = location.getPlotArea(); 1290 List<org.bukkit.block.BlockState> blocks = event.getBlocks(); 1291 if (area == null) { 1292 blocks.removeIf(block -> BukkitUtil.adapt(block.getLocation()).isPlotArea()); 1293 } else { 1294 Plot origin = area.getOwnedPlot(location); 1295 blocks.removeIf(block -> { 1296 Location blockLocation = BukkitUtil.adapt(block.getLocation()); 1297 if (!area.contains(blockLocation.getX(), blockLocation.getZ())) { 1298 return true; 1299 } 1300 Plot plot = area.getOwnedPlot(blockLocation); 1301 if (!Objects.equals(plot, origin)) { 1302 return true; 1303 } 1304 return !area.buildRangeContainsY(location.getY()); 1305 }); 1306 } 1307 if (blocks.isEmpty()) { 1308 // Cancel event so the sponge block doesn't turn into a wet sponge 1309 // if no water is being absorbed 1310 event.setCancelled(true); 1311 } 1312 } 1313 1314 /* 1315 * BlockMultiPlaceEvent is called unrelated to the BlockPlaceEvent itself and therefore doesn't respect the cancellation. 1316 */ 1317 @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) 1318 public void onBlockMultiPlace(BlockMultiPlaceEvent event) { 1319 // Check if the generic block place event would be cancelled 1320 blockCreate(event); 1321 if (event.isCancelled()) { 1322 return; 1323 } 1324 1325 BukkitPlayer pp = BukkitUtil.adapt(event.getPlayer()); 1326 Location placedLocation = BukkitUtil.adapt(event.getBlockReplacedState().getLocation()); 1327 PlotArea area = placedLocation.getPlotArea(); 1328 if (area == null) { 1329 return; 1330 } 1331 Plot plot = placedLocation.getPlot(); 1332 1333 for (final BlockState state : event.getReplacedBlockStates()) { 1334 Location currentLocation = BukkitUtil.adapt(state.getLocation()); 1335 if (!pp.hasPermission( 1336 Permission.PERMISSION_ADMIN_BUILD_ROAD 1337 ) && !(Objects.equals(currentLocation.getPlot(), plot))) { 1338 pp.sendMessage( 1339 TranslatableCaption.of("permission.no_permission_event"), 1340 TagResolver.resolver("node", Tag.inserting(Permission.PERMISSION_ADMIN_BUILD_ROAD)) 1341 ); 1342 event.setCancelled(true); 1343 break; 1344 } 1345 if (pp.hasPermission(Permission.PERMISSION_ADMIN_BUILD_HEIGHT_LIMIT)) { 1346 continue; 1347 } 1348 if (currentLocation.getY() >= area.getMaxBuildHeight() || currentLocation.getY() < area.getMinBuildHeight()) { 1349 pp.sendMessage( 1350 TranslatableCaption.of("height.height_limit"), 1351 TagResolver.builder() 1352 .tag("minheight", Tag.inserting(Component.text(area.getMinBuildHeight()))) 1353 .tag("maxheight", Tag.inserting(Component.text(area.getMaxBuildHeight()))) 1354 .build() 1355 ); 1356 if (area.notifyIfOutsideBuildArea(pp, currentLocation.getY())) { 1357 event.setCancelled(true); 1358 break; 1359 } 1360 } 1361 } 1362 1363 } 1364 1365}