/* * WorldGuard, a suite of tools for Minecraft * Copyright (C) sk89q * Copyright (C) WorldGuard team and contributors * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by the * Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ package com.sk89q.worldguard.bukkit.listener; import static com.sk89q.worldguard.bukkit.cause.Cause.create; import com.destroystokyo.paper.event.entity.EntityZapEvent; import com.sk89q.worldguard.bukkit.WorldGuardPlugin; import com.sk89q.worldguard.bukkit.cause.Cause; import com.sk89q.worldguard.bukkit.event.DelegateEvent; import com.sk89q.worldguard.bukkit.event.block.BreakBlockEvent; import com.sk89q.worldguard.bukkit.event.block.PlaceBlockEvent; import com.sk89q.worldguard.bukkit.event.block.UseBlockEvent; import com.sk89q.worldguard.bukkit.event.entity.DamageEntityEvent; import com.sk89q.worldguard.bukkit.event.entity.DestroyEntityEvent; import com.sk89q.worldguard.bukkit.event.entity.SpawnEntityEvent; import com.sk89q.worldguard.bukkit.event.entity.UseEntityEvent; import com.sk89q.worldguard.bukkit.event.inventory.UseItemEvent; import com.sk89q.worldguard.bukkit.listener.debounce.BlockPistonExtendKey; import com.sk89q.worldguard.bukkit.listener.debounce.BlockPistonRetractKey; import com.sk89q.worldguard.bukkit.listener.debounce.EventDebounce; import com.sk89q.worldguard.bukkit.listener.debounce.legacy.AbstractEventDebounce.Entry; import com.sk89q.worldguard.bukkit.listener.debounce.legacy.BlockEntityEventDebounce; import com.sk89q.worldguard.bukkit.listener.debounce.legacy.EntityEntityEventDebounce; import com.sk89q.worldguard.bukkit.listener.debounce.legacy.InventoryMoveItemEventDebounce; import com.sk89q.worldguard.bukkit.util.Blocks; import com.sk89q.worldguard.bukkit.util.Entities; import com.sk89q.worldguard.bukkit.util.Events; import com.sk89q.worldguard.bukkit.util.Materials; import com.sk89q.worldguard.config.WorldConfiguration; import com.sk89q.worldguard.protection.flags.Flags; import io.papermc.lib.PaperLib; import org.bukkit.Bukkit; import org.bukkit.Effect; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Tag; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.BlockState; import org.bukkit.block.Chest; import org.bukkit.block.DoubleChest; import org.bukkit.block.Hopper; import org.bukkit.block.PistonMoveReaction; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.Waterlogged; import org.bukkit.block.data.type.Dispenser; import org.bukkit.entity.AreaEffectCloud; import org.bukkit.entity.Creeper; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.FallingBlock; import org.bukkit.entity.Firework; import org.bukkit.entity.Item; import org.bukkit.entity.ItemFrame; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Mob; import org.bukkit.entity.Painting; import org.bukkit.entity.Player; import org.bukkit.entity.Tameable; import org.bukkit.entity.ThrownPotion; import org.bukkit.entity.minecart.HopperMinecart; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.Event.Result; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.block.Action; import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockBurnEvent; import org.bukkit.event.block.BlockDamageEvent; import org.bukkit.event.block.BlockDispenseEvent; import org.bukkit.event.block.BlockExpEvent; import org.bukkit.event.block.BlockExplodeEvent; import org.bukkit.event.block.BlockFertilizeEvent; import org.bukkit.event.block.BlockFromToEvent; import org.bukkit.event.block.BlockIgniteEvent; import org.bukkit.event.block.BlockMultiPlaceEvent; import org.bukkit.event.block.BlockPistonExtendEvent; import org.bukkit.event.block.BlockPistonRetractEvent; import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.block.CauldronLevelChangeEvent; import org.bukkit.event.block.EntityBlockFormEvent; import org.bukkit.event.block.SignChangeEvent; import org.bukkit.event.entity.AreaEffectCloudApplyEvent; import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.EntityChangeBlockEvent; import org.bukkit.event.entity.EntityCombustByBlockEvent; import org.bukkit.event.entity.EntityCombustByEntityEvent; import org.bukkit.event.entity.EntityCombustEvent; import org.bukkit.event.entity.EntityDamageByBlockEvent; import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.event.entity.EntityInteractEvent; import org.bukkit.event.entity.EntityPickupItemEvent; import org.bukkit.event.entity.EntityTameEvent; import org.bukkit.event.entity.EntityUnleashEvent; import org.bukkit.event.entity.ExpBottleEvent; import org.bukkit.event.entity.LingeringPotionSplashEvent; import org.bukkit.event.entity.PotionSplashEvent; import org.bukkit.event.hanging.HangingBreakByEntityEvent; import org.bukkit.event.hanging.HangingBreakEvent; import org.bukkit.event.hanging.HangingPlaceEvent; import org.bukkit.event.inventory.InventoryMoveItemEvent; import org.bukkit.event.inventory.InventoryOpenEvent; import org.bukkit.event.player.PlayerBedEnterEvent; import org.bukkit.event.player.PlayerBucketEmptyEvent; import org.bukkit.event.player.PlayerBucketFillEvent; import org.bukkit.event.player.PlayerDropItemEvent; import org.bukkit.event.player.PlayerFishEvent; import org.bukkit.event.player.PlayerInteractAtEntityEvent; import org.bukkit.event.player.PlayerInteractEntityEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerItemConsumeEvent; import org.bukkit.event.player.PlayerPickupItemEvent; import org.bukkit.event.player.PlayerShearEntityEvent; import org.bukkit.event.player.PlayerTakeLecternBookEvent; import org.bukkit.event.player.PlayerUnleashEntityEvent; import org.bukkit.event.vehicle.VehicleDamageEvent; import org.bukkit.event.vehicle.VehicleDestroyEvent; import org.bukkit.event.world.StructureGrowEvent; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.PluginManager; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.bukkit.projectiles.ProjectileSource; import org.bukkit.util.Vector; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; public class EventAbstractionListener extends AbstractListener { private final BlockEntityEventDebounce interactDebounce = new BlockEntityEventDebounce(10000); private final EntityEntityEventDebounce pickupDebounce = new EntityEntityEventDebounce(10000); private final BlockEntityEventDebounce entityBreakBlockDebounce = new BlockEntityEventDebounce(10000); private final InventoryMoveItemEventDebounce moveItemDebounce = new InventoryMoveItemEventDebounce(30000); private final EventDebounce pistonRetractDebounce = EventDebounce.create(5000); private final EventDebounce pistonExtendDebounce = EventDebounce.create(5000); /** * Construct the listener. * * @param plugin an instance of WorldGuardPlugin */ public EventAbstractionListener(WorldGuardPlugin plugin) { super(plugin); } @Override public void registerEvents() { super.registerEvents(); if (PaperLib.isPaper()) { PluginManager pm = getPlugin().getServer().getPluginManager(); pm.registerEvents(new EventAbstractionListener.PaperListener(), getPlugin()); } } //------------------------------------------------------------------------- // Block break / place //------------------------------------------------------------------------- @EventHandler(ignoreCancelled = true) public void onBlockBreak(BlockBreakEvent event) { Events.fireToCancel(event, new BreakBlockEvent(event, create(event.getPlayer()), event.getBlock())); if (event.isCancelled()) { playDenyEffect(event.getPlayer(), event.getBlock().getLocation().add(0.5, 1, 0.5)); } } @EventHandler(ignoreCancelled = true) public void onBlockMultiPlace(BlockMultiPlaceEvent event) { List placed = event.getReplacedBlockStates().stream().map(BlockState::getBlock).collect(Collectors.toList()); int origAmt = placed.size(); PlaceBlockEvent delegateEvent = new PlaceBlockEvent(event, create(event.getPlayer()), event.getBlock().getWorld(), placed, event.getBlockPlaced().getType()); Events.fireToCancel(event, delegateEvent); if (origAmt != placed.size()) { event.setCancelled(true); } } @EventHandler(ignoreCancelled = true) public void onBlockPlace(BlockPlaceEvent event) { if (event instanceof BlockMultiPlaceEvent) return; BlockState previousState = event.getBlockReplacedState(); // Some blocks, like tall grass and fire, get replaced if (previousState.getType() != Material.AIR && previousState.getType() != event.getBlockReplacedState().getType()) { Events.fireToCancel(event, new BreakBlockEvent(event, create(event.getPlayer()), previousState.getLocation(), previousState.getType())); } if (!event.isCancelled()) { ItemStack itemStack = new ItemStack(event.getBlockPlaced().getType(), 1); Events.fireToCancel(event, new UseItemEvent(event, create(event.getPlayer()), event.getPlayer().getWorld(), itemStack)); } if (!event.isCancelled()) { Events.fireToCancel(event, new PlaceBlockEvent(event, create(event.getPlayer()), event.getBlock())); } if (event.isCancelled()) { playDenyEffect(event.getPlayer(), event.getBlockPlaced().getLocation().add(0.5, 0.5, 0.5)); } } @EventHandler(ignoreCancelled = true) public void onBlockBurn(BlockBurnEvent event) { Block target = event.getBlock(); Block[] adjacent = { target.getRelative(BlockFace.NORTH), target.getRelative(BlockFace.SOUTH), target.getRelative(BlockFace.WEST), target.getRelative(BlockFace.EAST), target.getRelative(BlockFace.UP), target.getRelative(BlockFace.DOWN)}; int found = 0; boolean allowed = false; for (Block source : adjacent) { if (Materials.isFire(source.getType())) { found++; if (Events.fireAndTestCancel(new BreakBlockEvent(event, create(source), target))) { source.setType(Material.AIR); } else { allowed = true; } } } if (found > 0 && !allowed) { event.setCancelled(true); } } @EventHandler(ignoreCancelled = true) public void onStructureGrowEvent(StructureGrowEvent event) { int originalCount = event.getBlocks().size(); Player player = event.getPlayer(); Events.fireBulkEventToCancel(event, new PlaceBlockEvent(event, create(player == null ? event.getLocation().getBlock() : player), event.getLocation().getWorld(), event.getBlocks())); if (!event.isCancelled() && event.getBlocks().size() != originalCount) { event.getLocation().getBlock().setType(Material.AIR); } } private void handleFallingBlock(EntityChangeBlockEvent event, boolean dropItem) { Entity entity = event.getEntity(); Block block = event.getBlock(); if (entity instanceof FallingBlock) { try { if (dropItem) { FallingBlock fallingBlock = (FallingBlock) entity; if (!fallingBlock.getDropItem()) return; final Material material = fallingBlock.getBlockData().getMaterial(); if (!material.isItem()) return; ItemStack itemStack = new ItemStack(material, 1); Item item = block.getWorld().dropItem(fallingBlock.getLocation(), itemStack); item.setVelocity(new Vector()); if (Events.fireAndTestCancel(new SpawnEntityEvent(event, create(block, entity), item))) { item.remove(); } } } finally { Cause.untrackParentCause(entity); } } } private void setDelegateEventMaterialOptions(DelegateEvent event, Material fromType, Material toType) { if (fromType == Material.FARMLAND && toType == Material.DIRT) { event.setSilent(true); event.getRelevantFlags().add(Flags.TRAMPLE_BLOCKS); } else if (Tag.REDSTONE_ORES.isTagged(fromType)) { event.setSilent(true); } else if (fromType == Material.BIG_DRIPLEAF && toType == Material.BIG_DRIPLEAF) { event.setSilent(true); event.getRelevantFlags().add(Flags.USE_DRIPLEAF); } } @EventHandler(ignoreCancelled = true) public void onEntityChangeBlock(EntityChangeBlockEvent event) { Block block = event.getBlock(); Entity entity = event.getEntity(); Material toType = event.getTo(); Material fromType = block.getType(); Cause cause = create(entity); // Fire two events: one as BREAK and one as PLACE if (toType != Material.AIR && fromType != Material.AIR) { BreakBlockEvent breakDelagate = new BreakBlockEvent(event, cause, block); setDelegateEventMaterialOptions(breakDelagate, fromType, toType); boolean denied; if (!(denied = Events.fireToCancel(event, breakDelagate))) { PlaceBlockEvent placeDelegate = new PlaceBlockEvent(event, cause, block.getLocation(), toType); setDelegateEventMaterialOptions(placeDelegate, fromType, toType); denied = Events.fireToCancel(event, placeDelegate); } if (denied && entity instanceof Player) { playDenyEffect((Player) entity, block.getLocation()); } handleFallingBlock(event, denied); } else if (toType == Material.AIR) { // Track the source so later we can create a proper chain of causes if (entity instanceof FallingBlock) { Cause.trackParentCause(entity, block); // Switch around the event Events.fireToCancel(event, new SpawnEntityEvent(event, create(block), entity)); } else { entityBreakBlockDebounce.debounce( block, event.getEntity(), event, new BreakBlockEvent(event, cause, block)); } } else { // toType != Material.AIR && fromType == Material.AIR boolean denied = Events.fireToCancel(event, new PlaceBlockEvent(event, cause, block.getLocation(), toType)); handleFallingBlock(event, denied); } } @EventHandler(ignoreCancelled = true) public void onEntityExplode(EntityExplodeEvent event) { Entity entity = event.getEntity(); Events.fireBulkEventToCancel(event, new BreakBlockEvent(event, create(entity), event.getLocation().getWorld(), event.blockList(), Material.AIR)); if (entity instanceof Creeper) { Cause.untrackParentCause(entity); } } @EventHandler(ignoreCancelled = true) public void onBlockPistonRetract(BlockPistonRetractEvent event) { if (event.isSticky()) { EventDebounce.Entry entry = pistonRetractDebounce.getIfNotPresent(new BlockPistonRetractKey(event), event); if (entry != null) { Block piston = event.getBlock(); Cause cause = create(piston); BlockFace direction = event.getDirection(); ArrayList blocks = new ArrayList<>(event.getBlocks()); int originalSize = blocks.size(); Events.fireBulkEventToCancel(event, new BreakBlockEvent(event, cause, event.getBlock().getWorld(), blocks, Material.AIR)); if (originalSize != blocks.size()) { event.setCancelled(true); return; } for (Block b : blocks) { Location loc = b.getRelative(direction).getLocation(); Events.fireToCancel(event, new PlaceBlockEvent(event, cause, loc, b.getType())); } entry.setCancelled(event.isCancelled()); if (event.isCancelled()) { playDenyEffect(piston.getLocation().add(0.5, 1, 0.5)); } } } } @EventHandler(ignoreCancelled = true) public void onBlockPistonExtend(BlockPistonExtendEvent event) { EventDebounce.Entry entry = pistonExtendDebounce.getIfNotPresent(new BlockPistonExtendKey(event), event); if (entry != null) { Cause cause = create(event.getBlock()); List blocks = new ArrayList<>(event.getBlocks()); int originalLength = blocks.size(); Events.fireBulkEventToCancel(event, new BreakBlockEvent(event, cause, event.getBlock().getWorld(), blocks, Material.AIR)); if (originalLength != blocks.size()) { event.setCancelled(true); return; } BlockFace dir = event.getDirection(); for (int i = 0; i < blocks.size(); i++) { Block existing = blocks.get(i); if (existing.getPistonMoveReaction() == PistonMoveReaction.MOVE || existing.getPistonMoveReaction() == PistonMoveReaction.PUSH_ONLY || existing.getType() == Material.PISTON || existing.getType() == Material.STICKY_PISTON) { blocks.set(i, existing.getRelative(dir)); } } Events.fireBulkEventToCancel(event, new PlaceBlockEvent(event, cause, event.getBlock().getWorld(), blocks, Material.STONE)); if (blocks.size() != originalLength) { event.setCancelled(true); } entry.setCancelled(event.isCancelled()); if (event.isCancelled()) { playDenyEffect(event.getBlock().getLocation().add(0.5, 1, 0.5)); } } } //------------------------------------------------------------------------- // Block external interaction //------------------------------------------------------------------------- @EventHandler(ignoreCancelled = true) public void onBlockDamage(BlockDamageEvent event) { Block target = event.getBlock(); // Previously, and perhaps still, the only way to catch cake eating // events was through here if (target.getType() == Material.CAKE) { Events.fireToCancel(event, new UseBlockEvent(event, create(event.getPlayer()), target)); } } @EventHandler public void onPlayerInteract(PlayerInteractEvent event) { Player player = event.getPlayer(); @Nullable ItemStack item = event.getItem(); Block clicked = event.getClickedBlock(); Block placed; boolean modifiesWorld; Cause cause = create(player); switch (event.getAction()) { case PHYSICAL: if (event.useInteractedBlock() != Result.DENY) { if (clicked.getType() == Material.FARMLAND || clicked.getType() == Material.TURTLE_EGG || clicked.getType() == Material.SNIFFER_EGG) { BreakBlockEvent breakDelagate = new BreakBlockEvent(event, cause, clicked); breakDelagate.setSilent(true); breakDelagate.getRelevantFlags().add(Flags.TRAMPLE_BLOCKS); boolean denied; if (!(denied = Events.fireToCancel(event, breakDelagate))) { PlaceBlockEvent placeDelegate = new PlaceBlockEvent(event, cause, clicked.getLocation(), clicked.getType() == Material.FARMLAND ? Material.DIRT : clicked.getType()); placeDelegate.setSilent(true); placeDelegate.getRelevantFlags().add(Flags.TRAMPLE_BLOCKS); denied = Events.fireToCancel(event, placeDelegate); } if (denied) { playDenyEffect(player, clicked.getLocation()); } return; } DelegateEvent firedEvent = new UseBlockEvent(event, cause, clicked).setAllowed(hasInteractBypass(clicked)); if (Tag.REDSTONE_ORES.isTagged(clicked.getType())) { firedEvent.setSilent(true); } if (clicked.getType() == Material.BIG_DRIPLEAF) { firedEvent.getRelevantFlags().add(Flags.USE_DRIPLEAF); firedEvent.setSilent(true); } interactDebounce.debounce(clicked, event.getPlayer(), event, firedEvent); if (event.useInteractedBlock() == Result.DENY) { playDenyEffect(player, clicked.getLocation().add(0, 1, 0)); } } break; case RIGHT_CLICK_BLOCK: if (event.useInteractedBlock() != Result.DENY) { placed = clicked.getRelative(event.getBlockFace()); // Re-used for dispensers handleBlockRightClick(event, create(event.getPlayer()), item, clicked, placed); } case LEFT_CLICK_BLOCK: if (event.useInteractedBlock() != Result.DENY) { placed = clicked.getRelative(event.getBlockFace()); // Only fire events for blocks that are modified when right clicked final boolean hasItemInteraction = item != null && isItemAppliedToBlock(item, clicked) && event.getAction() == Action.RIGHT_CLICK_BLOCK; modifiesWorld = hasItemInteraction || isBlockModifiedOnClick(clicked, event.getAction() == Action.RIGHT_CLICK_BLOCK); if (Events.fireAndTestCancel(new UseBlockEvent(event, cause, clicked).setAllowed(!modifiesWorld))) { event.setUseInteractedBlock(Result.DENY); } // Handle connected blocks (i.e. beds, chests) for (Block connected : Blocks.getConnected(clicked)) { if (Events.fireAndTestCancel(new UseBlockEvent(event, create(event.getPlayer()), connected).setAllowed(!modifiesWorld))) { event.setUseInteractedBlock(Result.DENY); break; } } if (hasItemInteraction) { if (Events.fireAndTestCancel(new PlaceBlockEvent(event, cause, clicked.getLocation(), clicked.getType()))) { event.setUseItemInHand(Result.DENY); event.setUseInteractedBlock(Result.DENY); } } // Special handling of putting out fires if (event.getAction() == Action.LEFT_CLICK_BLOCK && Materials.isFire(placed.getType())) { if (Events.fireAndTestCancel(new BreakBlockEvent(event, create(event.getPlayer()), placed))) { event.setUseInteractedBlock(Result.DENY); break; } } if (event.isCancelled()) { playDenyEffect(event.getPlayer(), clicked.getLocation().add(0.5, 1, 0.5)); } } case LEFT_CLICK_AIR: case RIGHT_CLICK_AIR: if (event.useItemInHand() != Result.DENY) { if (item != null && !item.getType().isBlock() && Events.fireAndTestCancel(new UseItemEvent(event, cause, player.getWorld(), item))) { event.setUseItemInHand(Result.DENY); } } // Check for items that the administrator has configured to // emit a "use block here" event where the player is // standing, which is a hack to protect items that don't // throw events if (item != null && getWorldConfig(player.getWorld()).blockUseAtFeet.test(item)) { if (Events.fireAndTestCancel(new UseBlockEvent(event, cause, player.getLocation().getBlock()))) { event.setUseInteractedBlock(Result.DENY); } } break; } } @EventHandler(ignoreCancelled = true) public void onEntityBlockForm(EntityBlockFormEvent event) { entityBreakBlockDebounce.debounce(event.getBlock(), event.getEntity(), event, new PlaceBlockEvent(event, create(event.getEntity()), event.getBlock().getLocation(), event.getNewState().getType())); } @EventHandler(ignoreCancelled = true) public void onEntityInteract(EntityInteractEvent event) { interactDebounce.debounce(event.getBlock(), event.getEntity(), event, new UseBlockEvent(event, create(event.getEntity()), event.getBlock()).setAllowed(hasInteractBypass(event.getBlock()))); } @EventHandler(ignoreCancelled = true) public void onBlockFertilize(BlockFertilizeEvent event) { if (event.getBlocks().isEmpty()) return; Cause cause = create(event.getPlayer(), event.getBlock()); Events.fireToCancel(event, new PlaceBlockEvent(event, cause, event.getBlock().getWorld(), event.getBlocks())); } @EventHandler(ignoreCancelled = true) public void onBlockIgnite(BlockIgniteEvent event) { Block block = event.getBlock(); Cause cause; // Find the cause if (event.getPlayer() != null) { cause = create(event.getPlayer()); } else if (event.getIgnitingEntity() != null) { cause = create(event.getIgnitingEntity()); } else if (event.getIgnitingBlock() != null) { cause = create(event.getIgnitingBlock()); } else { cause = Cause.unknown(); } Events.fireToCancel(event, new PlaceBlockEvent(event, cause, block.getLocation(), Material.FIRE)); } @EventHandler(ignoreCancelled = true) public void onSignChange(SignChangeEvent event) { if (Events.fireToCancel(event, new PlaceBlockEvent(event, create(event.getPlayer()), event.getBlock()))) { playDenyEffect(event.getPlayer(), event.getBlock().getLocation().add(0.5, 0.5, 0.5)); } } @EventHandler(ignoreCancelled = true) public void onBedEnter(PlayerBedEnterEvent event) { Events.fireToCancel(event, new UseBlockEvent(event, create(event.getPlayer()), event.getBed())); } @EventHandler(ignoreCancelled = true) public void onPlayerBucketEmpty(PlayerBucketEmptyEvent event) { Player player = event.getPlayer(); Block blockClicked = event.getBlockClicked(); Block blockAffected; if (blockClicked.getBlockData() instanceof Waterlogged) { blockAffected = blockClicked; } else { blockAffected = blockClicked.getRelative(event.getBlockFace()); } boolean allowed = false; // Milk buckets can't be emptied as of writing if (event.getBucket() == Material.MILK_BUCKET) { allowed = true; } ItemStack item = new ItemStack(event.getBucket(), 1); Material blockMaterial = Materials.getBucketBlockMaterial(event.getBucket()); Events.fireToCancel(event, new PlaceBlockEvent(event, create(player), blockAffected.getLocation(), blockMaterial).setAllowed(allowed)); Events.fireToCancel(event, new UseItemEvent(event, create(player), player.getWorld(), item).setAllowed(allowed)); if (event.isCancelled()) { playDenyEffect(event.getPlayer(), blockAffected.getLocation().add(0.5, 0.5, 0.5)); } } @EventHandler(ignoreCancelled = true) public void onPlayerBucketFill(PlayerBucketFillEvent event) { Player player = event.getPlayer(); Block blockAffected = event.getBlockClicked().getRelative(event.getBlockFace()); boolean allowed = false; // Milk buckets can't be emptied as of writing if (event.getItemStack().getType() == Material.MILK_BUCKET) { allowed = true; } ItemStack item = new ItemStack(event.getBucket(), 1); Events.fireToCancel(event, new BreakBlockEvent(event, create(player), blockAffected).setAllowed(allowed)); Events.fireToCancel(event, new UseItemEvent(event, create(player), player.getWorld(), item).setAllowed(allowed)); if (event.isCancelled()) { playDenyEffect(event.getPlayer(), blockAffected.getLocation().add(0.5, 0.5, 0.5)); } } // TODO: Handle EntityPortalEnterEvent //------------------------------------------------------------------------- // Block self-interaction //------------------------------------------------------------------------- @EventHandler(ignoreCancelled = true) public void onBlockFromTo(BlockFromToEvent event) { WorldConfiguration config = getWorldConfig(event.getBlock().getWorld()); // This only applies to regions but nothing else cares about high // frequency events at the moment if (!config.useRegions || (!config.highFreqFlags && !config.checkLiquidFlow)) { return; } Block from = event.getBlock(); Block to = event.getToBlock(); Material fromType = from.getType(); Material toType = to.getType(); // Liquids pass this event when flowing to solid blocks if (Materials.isLiquid(fromType) && toType.isSolid()) { return; } // This significantly reduces the number of events without having // too much effect. Unfortunately it appears that even if this // check didn't exist, you can raise the level of some liquid // flow and the from/to data may not be correct. if ((Materials.isWater(fromType) && Materials.isWater(toType)) || (Materials.isLava(fromType) && Materials.isLava(toType))) { return; } Cause cause = create(from); // Disable since it's probably not needed /*if (from.getType() != Material.AIR) { Events.fireToCancel(event, new BreakBlockEvent(event, cause, to)); }*/ Events.fireToCancel(event, new PlaceBlockEvent(event, cause, to.getLocation(), from.getType())); } //------------------------------------------------------------------------- // Entity break / place //------------------------------------------------------------------------- @EventHandler(ignoreCancelled = true) public void onCreatureSpawn(CreatureSpawnEvent event) { switch (event.getSpawnReason()) { case DISPENSE_EGG: case EGG: case SPAWNER_EGG: if (getWorldConfig(event.getEntity().getWorld()).strictEntitySpawn) { Events.fireToCancel(event, new SpawnEntityEvent(event, Cause.unknown(), event.getEntity())); } break; default: } } @EventHandler(ignoreCancelled = true) public void onHangingPlace(HangingPlaceEvent event) { Events.fireToCancel(event, new SpawnEntityEvent(event, create(event.getPlayer()), event.getEntity())); if (event.isCancelled()) { Block effectBlock = event.getBlock().getRelative(event.getBlockFace()); playDenyEffect(event.getPlayer(), effectBlock.getLocation().add(0.5, 0.5, 0.5)); } } @EventHandler(ignoreCancelled = true) public void onHangingBreak(HangingBreakEvent event) { if (event instanceof HangingBreakByEntityEvent) { Entity remover = ((HangingBreakByEntityEvent) event).getRemover(); Events.fireToCancel(event, new DestroyEntityEvent(event, create(remover), event.getEntity())); if (event.isCancelled() && remover instanceof Player) { playDenyEffect((Player) remover, event.getEntity().getLocation()); } } else if (event.getCause() == HangingBreakEvent.RemoveCause.EXPLOSION){ DestroyEntityEvent destroyEntityEvent = new DestroyEntityEvent(event, Cause.unknown(), event.getEntity()); destroyEntityEvent.getRelevantFlags().add(Flags.OTHER_EXPLOSION); if (event.getEntity() instanceof ItemFrame) { destroyEntityEvent.getRelevantFlags().add(Flags.ENTITY_ITEM_FRAME_DESTROY); } else if (event.getEntity() instanceof Painting) { destroyEntityEvent.getRelevantFlags().add(Flags.ENTITY_PAINTING_DESTROY); } Events.fireToCancel(event, destroyEntityEvent); } } @EventHandler(ignoreCancelled = true) public void onVehicleDestroy(VehicleDestroyEvent event) { Events.fireToCancel(event, new DestroyEntityEvent(event, create(event.getAttacker()), event.getVehicle())); } @EventHandler(ignoreCancelled = true) public void onBlockExp(BlockExpEvent event) { if (event.getExpToDrop() > 0) { // Event is raised even where no XP is being dropped if (Events.fireAndTestCancel(new SpawnEntityEvent(event, create(event.getBlock()), event.getBlock().getLocation(), EntityType.EXPERIENCE_ORB))) { event.setExpToDrop(0); } } } @EventHandler(ignoreCancelled = true) public void onPlayerFish(PlayerFishEvent event) { if (event.getState() == PlayerFishEvent.State.FISHING) { if (Events.fireAndTestCancel(new UseItemEvent(event, create(event.getPlayer(), event.getHook()), event.getPlayer().getWorld(), event.getPlayer().getInventory().getItemInMainHand()))) { event.setCancelled(true); } } else if (event.getState() == PlayerFishEvent.State.CAUGHT_FISH) { if (event.getExpToDrop() > 0 && Events.fireAndTestCancel(new SpawnEntityEvent(event, create(event.getPlayer(), event.getHook()), event.getHook().getLocation(), EntityType.EXPERIENCE_ORB))) { event.setExpToDrop(0); } } else if (event.getState() == PlayerFishEvent.State.CAUGHT_ENTITY) { Entity caught = event.getCaught(); if (caught == null) return; if (caught instanceof Item) { Events.fireToCancel(event, new DestroyEntityEvent(event, create(event.getPlayer(), event.getHook()), caught)); } else if (Entities.isConsideredBuildingIfUsed(caught)) { Events.fireToCancel(event, new UseEntityEvent(event, create(event.getPlayer(), event.getHook()), caught)); } else if (Entities.isNonHostile(caught) || caught instanceof Player) { Events.fireToCancel(event, new DamageEntityEvent(event, create(event.getPlayer(), event.getHook()), caught)); } } } @EventHandler(ignoreCancelled = true) public void onExpBottle(ExpBottleEvent event) { if (Events.fireAndTestCancel(new SpawnEntityEvent(event, create(event.getEntity()), event.getEntity().getLocation(), EntityType.EXPERIENCE_ORB))) { event.setExperience(0); // Give the player back his or her XP bottle ProjectileSource shooter = event.getEntity().getShooter(); if (shooter instanceof Player) { Player player = (Player) shooter; if (player.getGameMode() != GameMode.CREATIVE) { player.getInventory().addItem(new ItemStack(Material.EXPERIENCE_BOTTLE, 1)); } } } } @EventHandler(ignoreCancelled = true) public void onEntityDeath(EntityDeathEvent event) { if (event.getDroppedExp() > 0) { if (Events.fireAndTestCancel(new SpawnEntityEvent(event, create(event.getEntity()), event.getEntity().getLocation(), EntityType.EXPERIENCE_ORB))) { event.setDroppedExp(0); } } } //------------------------------------------------------------------------- // Entity external interaction //------------------------------------------------------------------------- @EventHandler(ignoreCancelled = true) public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { Player player = event.getPlayer(); World world = player.getWorld(); ItemStack item = event.getHand() == EquipmentSlot.OFF_HAND ? player.getInventory().getItemInOffHand() : player.getInventory().getItemInMainHand(); Entity entity = event.getRightClicked(); if (Events.fireToCancel(event, new UseItemEvent(event, create(player), world, item))) { return; } final UseEntityEvent useEntityEvent = new UseEntityEvent(event, create(player), entity); Material matchingItem = Materials.getRelatedMaterial(entity.getType()); if (matchingItem != null && hasInteractBypass(world, matchingItem)) { useEntityEvent.setAllowed(true); } if (!Events.fireToCancel(event, useEntityEvent)) { // so this is a hack but CreeperIgniteEvent doesn't actually tell us who, so we need to do it here if (item.getType() == Material.FLINT_AND_STEEL && entity.getType() == EntityType.CREEPER) { Cause.trackParentCause(entity, player); } } } @EventHandler(ignoreCancelled = true) public void onEntityDamage(EntityDamageEvent event) { if (event instanceof EntityDamageByBlockEvent) { @Nullable Block attacker = ((EntityDamageByBlockEvent) event).getDamager(); // The attacker should NOT be null, but sometimes it is // See WORLDGUARD-3350 if (attacker != null) { Events.fireToCancel(event, new DamageEntityEvent(event, create(attacker), event.getEntity())); } } else if (event instanceof EntityDamageByEntityEvent) { EntityDamageByEntityEvent entityEvent = (EntityDamageByEntityEvent) event; Entity damager = entityEvent.getDamager(); final DamageEntityEvent eventToFire = new DamageEntityEvent(event, create(damager), event.getEntity()); if (damager instanceof Firework) { eventToFire.getRelevantFlags().add(Flags.FIREWORK_DAMAGE); } else if (damager instanceof Creeper) { eventToFire.getRelevantFlags().add(Flags.CREEPER_EXPLOSION); } if (Events.fireToCancel(event, eventToFire)) { if (damager instanceof Tameable && damager instanceof Mob) { ((Mob) damager).setTarget(null); } } // Item use event with the item in hand // Older blacklist handler code used this, although it suffers from // race problems if (damager instanceof Player) { // this event doesn't tell us which hand the weapon was in ItemStack item = ((Player) damager).getInventory().getItemInMainHand(); if (item.getType() != Material.AIR) { Events.fireToCancel(event, new UseItemEvent(event, create(damager), event.getEntity().getWorld(), item)); } } } } @EventHandler(ignoreCancelled = true) public void onEntityCombust(EntityCombustEvent event) { if (event instanceof EntityCombustByBlockEvent) { // at the time of writing, spigot is throwing null for the event's combuster. this causes lots of issues downstream. // whenever (i mean if ever) it is fixed, use getCombuster again instead of the current block Events.fireToCancel(event, new DamageEntityEvent(event, create(event.getEntity().getLocation().getBlock()), event.getEntity())); } else if (event instanceof EntityCombustByEntityEvent) { Events.fireToCancel(event, new DamageEntityEvent(event, create(((EntityCombustByEntityEvent) event).getCombuster()), event.getEntity())); } } @EventHandler(ignoreCancelled = true) public void onEntityUnleash(EntityUnleashEvent event) { if (event instanceof PlayerUnleashEntityEvent) { PlayerUnleashEntityEvent playerEvent = (PlayerUnleashEntityEvent) event; Events.fireToCancel(playerEvent, new UseEntityEvent(playerEvent, create(playerEvent.getPlayer()), event.getEntity())); } } @EventHandler(ignoreCancelled = true) public void onEntityTame(EntityTameEvent event) { Events.fireToCancel(event, new UseEntityEvent(event, create(event.getOwner()), event.getEntity())); } @EventHandler(ignoreCancelled = true) public void onPlayerShearEntity(PlayerShearEntityEvent event) { Events.fireToCancel(event, new UseEntityEvent(event, create(event.getPlayer()), event.getEntity())); } @EventHandler(ignoreCancelled = true) public void onPlayerPickupItem(PlayerPickupItemEvent event) { Item item = event.getItem(); pickupDebounce.debounce(event.getPlayer(), item, event, new DestroyEntityEvent(event, create(event.getPlayer()), event.getItem())); } @EventHandler(ignoreCancelled = true) public void onEntityPickupItem(EntityPickupItemEvent event) { Item item = event.getItem(); pickupDebounce.debounce(event.getEntity(), item, event, new DestroyEntityEvent(event, create(event.getEntity()), event.getItem())); } @EventHandler(ignoreCancelled = true) public void onPlayerDropItem(PlayerDropItemEvent event) { Events.fireToCancel(event, new SpawnEntityEvent(event, create(event.getPlayer()), event.getItemDrop())); } @EventHandler(ignoreCancelled = true) public void onVehicleDamage(VehicleDamageEvent event) { Entity attacker = event.getAttacker(); Events.fireToCancel(event, new DamageEntityEvent(event, create(attacker), event.getVehicle())); } //------------------------------------------------------------------------- // Composite events //------------------------------------------------------------------------- @EventHandler(ignoreCancelled = true) public void onPlayerItemConsume(PlayerItemConsumeEvent event) { Events.fireToCancel(event, new UseItemEvent(event, create(event.getPlayer()), event.getPlayer().getWorld(), event.getItem())); } @EventHandler(ignoreCancelled = true) public void onInventoryOpen(InventoryOpenEvent event) { InventoryHolder holder = event.getInventory().getHolder(); if (holder instanceof Entity && holder == event.getPlayer()) return; handleInventoryHolderUse(event, create(event.getPlayer()), holder); } @EventHandler(ignoreCancelled = true) public void onInventoryMoveItem(InventoryMoveItemEvent event) { InventoryHolder causeHolder = PaperLib.getHolder(event.getInitiator(), false).getHolder(); WorldConfiguration wcfg = null; if (causeHolder instanceof Hopper && (wcfg = getWorldConfig((((Hopper) causeHolder).getWorld()))).ignoreHopperMoveEvents) { return; } else if (causeHolder instanceof HopperMinecart && (wcfg = getWorldConfig((((HopperMinecart) causeHolder).getWorld()))).ignoreHopperMoveEvents) { return; } Entry entry; if ((entry = moveItemDebounce.tryDebounce(event)) != null) { InventoryHolder sourceHolder = PaperLib.getHolder(event.getSource(), false).getHolder(); InventoryHolder targetHolder = PaperLib.getHolder(event.getDestination(), false).getHolder(); Cause cause; if (causeHolder instanceof Entity) { cause = create(causeHolder); } else if (causeHolder instanceof BlockState) { cause = create(((BlockState) causeHolder).getBlock()); } else { cause = Cause.unknown(); } if (causeHolder != null && !causeHolder.equals(sourceHolder)) { handleInventoryHolderUse(event, cause, sourceHolder); } handleInventoryHolderUse(event, cause, targetHolder); if (event.isCancelled() && causeHolder instanceof Hopper && wcfg.breakDeniedHoppers) { Bukkit.getScheduler().scheduleSyncDelayedTask(getPlugin(), () -> ((Hopper) causeHolder).getBlock().breakNaturally()); } else { entry.setCancelled(event.isCancelled()); } } } @EventHandler(ignoreCancelled = true) public void onPotionSplash(PotionSplashEvent event) { Entity entity = event.getEntity(); ThrownPotion potion = event.getPotion(); World world = entity.getWorld(); Cause cause = create(potion); // Fire item interaction event Events.fireToCancel(event, new UseItemEvent(event, cause, world, potion.getItem())); // Fire entity interaction event if (!event.isCancelled()) { int blocked = 0; int affectedSize = event.getAffectedEntities().size(); boolean hasDamageEffect = Materials.hasDamageEffect(potion.getEffects()); for (LivingEntity affected : event.getAffectedEntities()) { DelegateEvent delegate = hasDamageEffect ? new DamageEntityEvent(event, cause, affected) : new UseEntityEvent(event, cause, affected); // Consider the potion splash flag delegate.getRelevantFlags().add(Flags.POTION_SPLASH); if (Events.fireAndTestCancel(delegate)) { event.setIntensity(affected, 0); blocked++; } } if (affectedSize > 0 && blocked == affectedSize) { // server does weird things with this if the event is modified, so use cached number event.setCancelled(true); } } } @EventHandler(ignoreCancelled = true) public void onBlockDispense(BlockDispenseEvent event) { Block dispenserBlock = event.getBlock(); // Simulate right click event as players have it if (dispenserBlock.getType() == Material.DISPENSER) { Cause cause = create(event.getBlock()); ItemStack item = event.getItem(); if (Events.fireToCancel(event, new UseItemEvent(event, cause, dispenserBlock.getWorld(), item))) { return; } BlockData blockData = dispenserBlock.getBlockData(); Dispenser dispenser = (Dispenser) blockData; // if this ClassCastExceptions it's a bukkit bug Block placed = dispenserBlock.getRelative(dispenser.getFacing()); Block clicked = placed.getRelative(dispenser.getFacing()); handleBlockRightClick(event, cause, item, clicked, placed); // handle special dispenser behavior if (Materials.isShulkerBox(item.getType())) { if (Events.fireToCancel(event, new PlaceBlockEvent(event, cause, placed.getLocation(), item.getType()))) { playDenyEffect(placed.getLocation()); } } else if (isItemAppliedToBlock(item, placed)) { if (Events.fireToCancel(event, new PlaceBlockEvent(event, cause, placed.getLocation(), placed.getType()))) { playDenyEffect(placed.getLocation()); } } } } @EventHandler(ignoreCancelled = true) public void onLingeringSplash(LingeringPotionSplashEvent event) { AreaEffectCloud aec = event.getAreaEffectCloud(); ThrownPotion potion = event.getEntity(); World world = potion.getWorld(); Cause cause = create(event.getEntity()); // Fire item interaction event Events.fireToCancel(event, new UseItemEvent(event, cause, world, potion.getItem())); // Fire entity spawn event if (!event.isCancelled()) { // radius unfortunately doesn't go through with this, so only a single location is tested Events.fireToCancel(event, new SpawnEntityEvent(event, cause, aec.getLocation().add(0.5, 0, 0.5), EntityType.AREA_EFFECT_CLOUD)); } } @EventHandler(ignoreCancelled = true) public void onLingeringApply(AreaEffectCloudApplyEvent event) { AreaEffectCloud entity = event.getEntity(); List effects = new ArrayList<>(); PotionEffectType baseEffectType = entity.getBasePotionData().getType().getEffectType(); if (baseEffectType != null) { effects.add(new PotionEffect(baseEffectType, 0, 0)); } if (entity.hasCustomEffects()) { effects.addAll(entity.getCustomEffects()); } if (!Materials.hasDamageEffect(effects)) { return; } Cause cause = create(event.getEntity()); event.getAffectedEntities() .removeIf(victim -> Events.fireAndTestCancel(new DamageEntityEvent(event, cause, victim))); } @EventHandler(ignoreCancelled = true) public void onPlayerInteractAtEntity(PlayerInteractAtEntityEvent event){ onPlayerInteractEntity(event); } @EventHandler(ignoreCancelled = true) public void onBlockExplode(BlockExplodeEvent event) { final BreakBlockEvent eventToFire = new BreakBlockEvent(event, create(event.getBlock()), event.getBlock().getLocation().getWorld(), event.blockList(), Material.AIR); eventToFire.getRelevantFlags().add(Flags.OTHER_EXPLOSION); Events.fireBulkEventToCancel(event, eventToFire); } @EventHandler(ignoreCancelled = true) public void onTakeLecternBook(PlayerTakeLecternBookEvent event) { final UseBlockEvent useEvent = new UseBlockEvent(event, create(event.getPlayer()), event.getLectern().getBlock()); Events.fireToCancel(event, useEvent); } @EventHandler(ignoreCancelled = true) public void onCauldronLevelChange(CauldronLevelChangeEvent event) { if (event.getEntity() == null) return; interactDebounce.debounce(event.getBlock(), event.getEntity(), event, new UseBlockEvent(event, create(event.getEntity()), event.getBlock()).setAllowed(hasInteractBypass(event.getBlock()))); } /** * Handle the right click of a block while an item is held. * * @param event the original event * @param cause the list of cause * @param item the item * @param placed the placed block * @param the event type */ private static void handleBlockRightClick(T event, Cause cause, @Nullable ItemStack item, Block clicked, Block placed) { if (item != null && item.getType() == Material.TNT) { // Workaround for a bug that allowed TNT to trigger instantly if placed // next to redstone, without plugins getting the clicked place event // (not sure if this actually still happens) -- note Jun 2019 - happens with dispensers still, tho not players Events.fireToCancel(event, new UseBlockEvent(event, cause, clicked.getLocation(), Material.TNT)); // Workaround for http://leaky.bukkit.org/issues/1034 Events.fireToCancel(event, new PlaceBlockEvent(event, cause, placed.getLocation(), Material.TNT)); return; } // Handle created Minecarts if (item != null && Materials.isMinecart(item.getType())) { EntityType entityType = Materials.getRelatedEntity(item.getType()); if (entityType == null) { entityType = EntityType.MINECART; } Events.fireToCancel(event, new SpawnEntityEvent(event, cause, clicked.getLocation().add(0.5, 0, 0.5), entityType)); return; } // Handle created boats if (item != null && Materials.isBoat(item.getType())) { Events.fireToCancel(event, new SpawnEntityEvent(event, cause, placed.getLocation().add(0.5, 0, 0.5), EntityType.BOAT)); return; } // Handle created armor stands if (item != null && item.getType() == Material.ARMOR_STAND) { Events.fireToCancel(event, new SpawnEntityEvent(event, cause, placed.getLocation().add(0.5, 0, 0.5), EntityType.ARMOR_STAND)); return; } if (item != null && item.getType() == Material.END_CRYSTAL) { /*&& placed.getType() == Material.BEDROCK) {*/ // in vanilla you can only place them on bedrock but who knows what plugins will add // may be overprotective as a result, but better than being underprotective Events.fireToCancel(event, new SpawnEntityEvent(event, cause, placed.getLocation().add(0.5, 0, 0.5), EntityType.ENDER_CRYSTAL)); return; } // Handle created spawn eggs if (item != null) { EntityType possibleEntityType = Materials.getEntitySpawnEgg(item.getType()); if (possibleEntityType != null) { Events.fireToCancel(event, new SpawnEntityEvent(event, cause, placed.getLocation().add(0.5, 0, 0.5), possibleEntityType)); return; } } // handle water/lava placement if (item != null && (item.getType() == Material.WATER_BUCKET || item.getType() == Material.LAVA_BUCKET)) { Events.fireToCancel(event, new PlaceBlockEvent(event, cause, placed.getLocation(), item.getType() == Material.WATER_BUCKET ? Material.WATER : Material.LAVA)); return; } } private static void handleInventoryHolderUse(T originalEvent, Cause cause, InventoryHolder holder) { if (originalEvent.isCancelled()) { return; } if (holder instanceof Entity entity) { Material mat = Materials.getRelatedMaterial((entity).getType()); UseEntityEvent useEntityEvent = new UseEntityEvent(originalEvent, cause, entity); if (mat != null && hasInteractBypass((entity).getWorld(), mat)) { useEntityEvent.setAllowed(true); } Events.fireToCancel(originalEvent, useEntityEvent); } else { if (holder instanceof BlockState block && block.isPlaced()) { final UseBlockEvent useBlockEvent = new UseBlockEvent(originalEvent, cause, block.getBlock()); if (hasInteractBypass(block.getWorld(), block.getType())) { useBlockEvent.setAllowed(true); } Events.fireToCancel(originalEvent, useBlockEvent); } else if (holder instanceof DoubleChest) { InventoryHolder left = ((DoubleChest) holder).getLeftSide(); InventoryHolder right = ((DoubleChest) holder).getRightSide(); if (left instanceof Chest) { Events.fireToCancel(originalEvent, new UseBlockEvent(originalEvent, cause, ((Chest) left).getBlock())); } if (right instanceof Chest) { Events.fireToCancel(originalEvent, new UseBlockEvent(originalEvent, cause, ((Chest) right).getBlock())); } } } } private static boolean hasInteractBypass(Block block) { return getWorldConfig(block.getWorld()).allowAllInteract.test(block); } private static boolean hasInteractBypass(World world, Material material) { return getWorldConfig(world).allowAllInteract.test(material); } private static boolean hasInteractBypass(World world, ItemStack item) { return getWorldConfig(world).allowAllInteract.test(item); } private static boolean isBlockModifiedOnClick(Block block, boolean rightClick) { return Materials.isBlockModifiedOnClick(block.getType(), rightClick) && !hasInteractBypass(block); } private static boolean isItemAppliedToBlock(ItemStack item, Block clicked) { return Materials.isItemAppliedToBlock(item.getType(), clicked.getType()) && !hasInteractBypass(clicked) && !hasInteractBypass(clicked.getWorld(), item); } private static void playDenyEffect(Player player, Location location) { //player.playSound(location, Sound.SUCCESSFUL_HIT, 0.2f, 0.4f); if (getConfig().particleEffects) { player.playEffect(location, Effect.SMOKE, BlockFace.UP); } } private static void playDenyEffect(Location location) { if (getConfig().particleEffects) { location.getWorld().playEffect(location, Effect.SMOKE, BlockFace.UP); } } private class PaperListener implements Listener { @EventHandler(ignoreCancelled = true) public void onEntityTransform(EntityZapEvent event) { Events.fireToCancel(event, new DamageEntityEvent(event, create(event.getBolt()), event.getEntity())); } } }