package tc.oc.pgm.modules; import javax.annotation.Nullable; import net.kyori.text.Component; import net.kyori.text.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.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.player.PlayerArmorStandManipulateEvent; 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.pgm.api.event.AdventureModeInteractEvent; import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.match.MatchModule; import tc.oc.pgm.api.match.MatchScope; import tc.oc.pgm.api.player.MatchPlayer; import tc.oc.pgm.api.player.MatchPlayerState; import tc.oc.pgm.api.player.event.ObserverInteractEvent; import tc.oc.pgm.events.ListenerScope; import tc.oc.pgm.events.PlayerBlockTransformEvent; /** * 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. * * <p>Any functionality beyond that should be implemented in other modules. This module should be * kept simple. */ @ListenerScope(MatchScope.LOADED) public class EventFilterMatchModule implements MatchModule, Listener { private final Match match; public EventFilterMatchModule(Match match) { this.match = match; } boolean cancel(Cancellable event, @Nullable MatchPlayer actor, @Nullable Component message) { match.getLogger().fine("Cancel " + event + " actor=" + actor); event.setCancelled(true); if (actor != null && message != null) { actor.sendWarning(message); } return true; } boolean cancel( Cancellable event, boolean cancel, World world, @Nullable MatchPlayer actor, @Nullable Component message) { if (cancel && match.getWorld().equals(world)) { return cancel(event, actor, message); } else { match.getLogger().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) { if (!(entity instanceof Player)) { return false; } return cancel( event, match.getParticipant(entity) == null, entity.getWorld(), match.getPlayer(entity), null); } boolean cancelUnlessInteracting(Cancellable event, MatchPlayerState player) { return cancel( event, !player.getParty().isParticipating(), 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(), match.getPlayer(event.getPlayer()), TranslatableComponent.of("match.disabled.bed")); } // --------------------------- // -- 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); } MatchPlayer player = match.getPlayer(event.getPlayer()); if (player == null) return; ClickType clickType = convertClick(event.getAction(), event.getPlayer()); if (clickType == null) return; match.callEvent( new ObserverInteractEvent( player, clickType, event.getClickedBlock(), null, event.getItem())); } } @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 = match.getPlayer(event.getPlayer()); if (player == null) return; match.callEvent( new ObserverInteractEvent( player, convertClick(ClickType.RIGHT, event.getPlayer()), null, event.getRightClicked(), event.getPlayer().getItemInHand())); } @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(), TranslatableComponent.of("match.disabled.enderChest")); } } @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 onDamage(final EntityDamageEvent event) { cancelUnlessInteracting(event, event.getEntity()); if (event instanceof EntityDamageByEntityEvent) { EntityDamageByEntityEvent entityEvent = (EntityDamageByEntityEvent) event; if (cancelUnlessInteracting(event, entityEvent.getDamager())) { MatchPlayer player = match.getPlayer(entityEvent.getDamager()); if (player == null) return; match.callEvent( new ObserverInteractEvent( player, ClickType.LEFT, null, event.getEntity(), player.getInventory().getItemInHand())); } } } @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 (entity instanceof Player && match.getParticipant(entity) == null) { event.setIntensity(entity, 0); } } } // ----------------------------------- // -- Player item/inventory actions -- // ----------------------------------- @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onPlayerDropItem(final PlayerDropItemEvent event) { if (match.getParticipant(event.getPlayer()) == null) { 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()); } }