package tc.oc.pgm.modules; import java.util.Iterator; import javax.annotation.Nullable; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.TranslatableComponent; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.Action; import org.bukkit.event.block.BlockDamageEvent; import org.bukkit.event.entity.AreaEffectCloudApplyEvent; import org.bukkit.event.entity.EntityCombustByEntityEvent; import org.bukkit.event.entity.EntityCombustEvent; import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityShootBowEvent; import org.bukkit.event.entity.EntityTargetEvent; import org.bukkit.event.entity.PotionSplashEvent; import org.bukkit.event.hanging.HangingBreakByEntityEvent; import org.bukkit.event.hanging.HangingBreakEvent; import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.player.PlayerArmorStandManipulateEvent; import org.bukkit.event.player.PlayerAttackEntityEvent; import org.bukkit.event.player.PlayerBedEnterEvent; import org.bukkit.event.player.PlayerDropItemEvent; import org.bukkit.event.player.PlayerInteractAtEntityEvent; import org.bukkit.event.player.PlayerInteractEntityEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerPickupItemEvent; import org.bukkit.event.vehicle.VehicleDamageEvent; import org.bukkit.event.vehicle.VehicleEnterEvent; import org.bukkit.event.vehicle.VehicleEntityCollisionEvent; import org.bukkit.event.weather.WeatherChangeEvent; import org.bukkit.event.world.PortalCreateEvent; import tc.oc.commons.bukkit.event.AdventureModeInteractEvent; import tc.oc.pgm.events.ListenerScope; import tc.oc.pgm.events.ObserverInteractEvent; import tc.oc.pgm.events.PlayerBlockTransformEvent; import tc.oc.pgm.match.MatchModule; import tc.oc.pgm.match.MatchPlayer; import tc.oc.pgm.match.MatchPlayerState; import tc.oc.pgm.match.MatchScope; /** * Listens to many events at low priority and cancels them if the actor is * not allowed to interact with the world. Also cancels a few events that * we just don't want ever. * * Any functionality beyond that should be implemented in other modules. * This module should be kept simple. */ @ListenerScope(MatchScope.LOADED) public class EventFilterMatchModule extends MatchModule implements Listener { boolean cancel(Cancellable event, @Nullable MatchPlayer actor, @Nullable BaseComponent message) { logger.fine("Cancel " + event + " actor=" + actor); event.setCancelled(true); if(actor != null && message != null) { actor.sendWarning(message, true); } return true; } boolean cancel(Cancellable event, boolean cancel, World world, @Nullable MatchPlayer actor, @Nullable BaseComponent message) { if(cancel && getMatch().getWorld().equals(world)) { return cancel(event, actor, message); } else { logger.fine("Allow " + event + " actor=" + actor); return false; } } boolean cancel(Cancellable event, boolean cancel, World world) { return cancel(event, cancel, world, null, null); } boolean cancelAlways(Cancellable event, World world) { return cancel(event, true, world); } boolean cancelUnlessInteracting(Cancellable event, MatchPlayer player) { return cancel(event, !player.canInteract(), player.getBukkit().getWorld(), player, null); } boolean cancelUnlessInteracting(Cancellable event, Entity entity) { return entity != null && cancel(event, !getMatch().canInteract(entity), entity.getWorld(), getMatch().getPlayer(entity), null); } boolean cancelUnlessInteracting(Cancellable event, MatchPlayerState player) { return cancel(event, !getMatch().canInteract(player), player.getMatch().getWorld(), null, null); } ClickType convertClick(ClickType clickType, Player player) { if(clickType == ClickType.RIGHT && player.isSneaking()) { return ClickType.SHIFT_RIGHT; } else { return clickType; } } @Nullable ClickType convertClick(Action action, Player player) { switch(action) { case LEFT_CLICK_BLOCK: case LEFT_CLICK_AIR: return ClickType.LEFT; case RIGHT_CLICK_BLOCK: case RIGHT_CLICK_AIR: return convertClick(ClickType.RIGHT, player); default: return null; } } // ------------------------------------------------------------- // -- Unconditionally cancelled events i.e. rejected features -- // ------------------------------------------------------------- @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onPortalCreate(final PortalCreateEvent event) { cancelAlways(event, event.getWorld()); } @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onWeatherChange(final WeatherChangeEvent event) { cancelAlways(event, event.getWorld()); } @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onBedEnter(final PlayerBedEnterEvent event) { cancel(event, true, event.getPlayer().getWorld(), getMatch().getPlayer(event.getPlayer()), new TranslatableComponent("match.bed.disabled")); } // --------------------------- // -- Player item/block use -- // --------------------------- // This handler listens on HIGHEST so that other plugins get a chance // to handle observer clicks before we cancel them i.e. WorldEdit. @EventHandler(priority = EventPriority.HIGHEST) public void onInteract(final PlayerInteractEvent event) { if(cancelUnlessInteracting(event, event.getPlayer())) { // Allow the how-to book to be read if(event.getMaterial() == Material.WRITTEN_BOOK) { event.setUseItemInHand(Event.Result.ALLOW); } else { event.setUseItemInHand(Event.Result.DENY); event.setUseInteractedBlock(Event.Result.DENY); } MatchPlayer player = getMatch().getPlayer(event.getPlayer()); if(player == null) return; if(!player.isSpawned()) { ClickType clickType = convertClick(event.getAction(), event.getPlayer()); if(clickType == null) return; getMatch().callEvent(new ObserverInteractEvent(player, clickType, event.getClickedBlock(), null, event.getItem())); } // Right-clicking armor will put it on unless we do this event.getPlayer().updateInventory(); } } @EventHandler(priority = EventPriority.LOWEST) public void onShoot(final EntityShootBowEvent event) { // PlayerInteractEvent is fired on draw, this is fired on release. Need to cancel both. cancelUnlessInteracting(event, event.getEntity()); } // -------------------------------------- // -- Player interaction with entities -- // -------------------------------------- void callObserverInteractEvent(PlayerInteractEntityEvent event) { MatchPlayer player = getMatch().getPlayer(event.getPlayer()); if(player == null || player.isSpawned()) return; getMatch().callEvent(new ObserverInteractEvent(player, convertClick(ClickType.RIGHT, event.getPlayer()), null, event.getRightClicked(), event.getPlayer().getInventory().getItem(event.getHand()))); } @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onEntityInteract(final PlayerInteractEntityEvent event) { if(cancelUnlessInteracting(event, event.getPlayer())) { callObserverInteractEvent(event); } } @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onArmorStandInteract(final PlayerInteractAtEntityEvent event) { cancelUnlessInteracting(event, event.getPlayer()); } @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onArmorStandInteract(final PlayerArmorStandManipulateEvent event) { cancelUnlessInteracting(event, event.getPlayer()); } // -------------------------------------- // -- Player interaction with vehicles -- // -------------------------------------- @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onVehicleDamage(final VehicleDamageEvent event) { cancelUnlessInteracting(event, event.getAttacker()); } @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onVehiclePush(final VehicleEntityCollisionEvent event) { cancelUnlessInteracting(event, event.getEntity()); } @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onVehicleEnter(final VehicleEnterEvent event) { cancelUnlessInteracting(event, event.getEntered()); } // ------------------------------------ // -- Player interaction with blocks -- // ------------------------------------ @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onPlayerBlockChange(final PlayerBlockTransformEvent event) { cancelUnlessInteracting(event, event.getPlayerState()); if(!event.isCancelled() && event.getNewState().getType() == Material.ENDER_CHEST) { cancel(event, true, event.getWorld(), event.getPlayer(), new TranslatableComponent("match.enderChestsDisabled")); } } @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onPlayerBlockDamage(final BlockDamageEvent event) { cancelUnlessInteracting(event, event.getPlayer()); } @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onHangingBreak(final HangingBreakEvent event) { cancelUnlessInteracting(event, event instanceof HangingBreakByEntityEvent ? ((HangingBreakByEntityEvent) event).getRemover() : null); } @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onAdventureModeInteract(final AdventureModeInteractEvent event) { cancelUnlessInteracting(event, event.getActor()); } // -------------------------- // -- Player damage/combat -- // -------------------------- @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onAttack(final PlayerAttackEntityEvent event) { if(cancelUnlessInteracting(event, event.getPlayer())) { final MatchPlayer attacker = getMatch().getPlayer(event.getPlayer()); if(attacker == null || attacker.isSpawned()) return; getMatch().callEvent(new ObserverInteractEvent(attacker, ClickType.LEFT, null, event.getLeftClicked(), event.getPlayer().getInventory().getItemInMainHand())); } } @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onDamage(final EntityDamageEvent event) { cancelUnlessInteracting(event, event.getEntity()); if(event instanceof EntityDamageByEntityEvent) { cancelUnlessInteracting(event, ((EntityDamageByEntityEvent) event).getDamager()); } } @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onCombust(final EntityCombustEvent event) { cancelUnlessInteracting(event, event.getEntity()); if(event instanceof EntityCombustByEntityEvent) { cancelUnlessInteracting(event, ((EntityCombustByEntityEvent) event).getCombuster()); } } @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onPotionSplash(final PotionSplashEvent event) { for(LivingEntity entity : event.getAffectedEntities()) { if(!getMatch().canInteract(entity)) { event.setIntensity(entity, 0); } } } @EventHandler(priority = EventPriority.LOWEST) public void onPotionLinger(final AreaEffectCloudApplyEvent event) { for(Iterator<LivingEntity> iterator = event.getAffectedEntities().iterator(); iterator.hasNext(); ) { if(!getMatch().canInteract(iterator.next())) { iterator.remove(); } } } // ----------------------------------- // -- Player item/inventory actions -- // ----------------------------------- @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onPlayerDropItem(final PlayerDropItemEvent event) { match.player(event.getPlayer()).ifPresent(player -> { if(!player.canInteract()) { if(player.isSpawned()) { // If player is spawned (but frozen), force them to keep the item event.setCancelled(true); } else { // If player is observing, just destroy the item event.getItemDrop().remove(); } } }); } @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onPlayerPickupItem(final PlayerPickupItemEvent event) { cancelUnlessInteracting(event, event.getPlayer()); } // ---------------------- // -- Player targeting -- // ---------------------- @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onEntityTrack(final EntityTargetEvent event) { // Handles mobs and XP orbs if(event.getTarget() != null) cancelUnlessInteracting(event, event.getTarget()); } }