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.destroystokyo.paper.MaterialTags;
022import com.google.common.base.Charsets;
023import com.google.inject.Inject;
024import com.plotsquared.bukkit.player.BukkitPlayer;
025import com.plotsquared.bukkit.util.BukkitEntityUtil;
026import com.plotsquared.bukkit.util.BukkitUtil;
027import com.plotsquared.bukkit.util.UpdateUtility;
028import com.plotsquared.core.PlotSquared;
029import com.plotsquared.core.configuration.Settings;
030import com.plotsquared.core.configuration.caption.Caption;
031import com.plotsquared.core.configuration.caption.TranslatableCaption;
032import com.plotsquared.core.listener.PlayerBlockEventType;
033import com.plotsquared.core.listener.PlotListener;
034import com.plotsquared.core.location.Location;
035import com.plotsquared.core.permissions.Permission;
036import com.plotsquared.core.player.ConsolePlayer;
037import com.plotsquared.core.player.MetaDataAccess;
038import com.plotsquared.core.player.PlayerMetaDataKeys;
039import com.plotsquared.core.player.PlotPlayer;
040import com.plotsquared.core.plot.Plot;
041import com.plotsquared.core.plot.PlotArea;
042import com.plotsquared.core.plot.PlotId;
043import com.plotsquared.core.plot.PlotInventory;
044import com.plotsquared.core.plot.flag.FlagContainer;
045import com.plotsquared.core.plot.flag.implementations.AnimalInteractFlag;
046import com.plotsquared.core.plot.flag.implementations.BlockedCmdsFlag;
047import com.plotsquared.core.plot.flag.implementations.ChatFlag;
048import com.plotsquared.core.plot.flag.implementations.DenyPortalTravelFlag;
049import com.plotsquared.core.plot.flag.implementations.DenyPortalsFlag;
050import com.plotsquared.core.plot.flag.implementations.DenyTeleportFlag;
051import com.plotsquared.core.plot.flag.implementations.DoneFlag;
052import com.plotsquared.core.plot.flag.implementations.DropProtectionFlag;
053import com.plotsquared.core.plot.flag.implementations.EditSignFlag;
054import com.plotsquared.core.plot.flag.implementations.HangingBreakFlag;
055import com.plotsquared.core.plot.flag.implementations.HangingPlaceFlag;
056import com.plotsquared.core.plot.flag.implementations.HostileInteractFlag;
057import com.plotsquared.core.plot.flag.implementations.ItemDropFlag;
058import com.plotsquared.core.plot.flag.implementations.KeepInventoryFlag;
059import com.plotsquared.core.plot.flag.implementations.LecternReadBookFlag;
060import com.plotsquared.core.plot.flag.implementations.MiscInteractFlag;
061import com.plotsquared.core.plot.flag.implementations.PlayerInteractFlag;
062import com.plotsquared.core.plot.flag.implementations.PreventCreativeCopyFlag;
063import com.plotsquared.core.plot.flag.implementations.TamedInteractFlag;
064import com.plotsquared.core.plot.flag.implementations.UntrustedVisitFlag;
065import com.plotsquared.core.plot.flag.implementations.VehicleBreakFlag;
066import com.plotsquared.core.plot.flag.implementations.VehicleUseFlag;
067import com.plotsquared.core.plot.flag.implementations.VillagerInteractFlag;
068import com.plotsquared.core.plot.world.PlotAreaManager;
069import com.plotsquared.core.util.EventDispatcher;
070import com.plotsquared.core.util.MathMan;
071import com.plotsquared.core.util.PlotFlagUtil;
072import com.plotsquared.core.util.PremiumVerification;
073import com.plotsquared.core.util.entity.EntityCategories;
074import com.plotsquared.core.util.task.TaskManager;
075import com.plotsquared.core.util.task.TaskTime;
076import com.sk89q.worldedit.WorldEdit;
077import com.sk89q.worldedit.bukkit.BukkitAdapter;
078import com.sk89q.worldedit.world.block.BlockType;
079import io.papermc.lib.PaperLib;
080import net.kyori.adventure.text.Component;
081import net.kyori.adventure.text.minimessage.MiniMessage;
082import net.kyori.adventure.text.minimessage.tag.Tag;
083import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
084import net.kyori.adventure.text.minimessage.tag.standard.StandardTags;
085import org.bukkit.Bukkit;
086import org.bukkit.FluidCollisionMode;
087import org.bukkit.Material;
088import org.bukkit.block.Block;
089import org.bukkit.block.BlockFace;
090import org.bukkit.block.BlockState;
091import org.bukkit.block.Sign;
092import org.bukkit.block.data.Waterlogged;
093import org.bukkit.command.PluginCommand;
094import org.bukkit.entity.ArmorStand;
095import org.bukkit.entity.Boat;
096import org.bukkit.entity.Entity;
097import org.bukkit.entity.EntityType;
098import org.bukkit.entity.HumanEntity;
099import org.bukkit.entity.ItemFrame;
100import org.bukkit.entity.LivingEntity;
101import org.bukkit.entity.Player;
102import org.bukkit.entity.Projectile;
103import org.bukkit.entity.Tameable;
104import org.bukkit.entity.Vehicle;
105import org.bukkit.event.Event;
106import org.bukkit.event.EventHandler;
107import org.bukkit.event.EventPriority;
108import org.bukkit.event.Listener;
109import org.bukkit.event.block.Action;
110import org.bukkit.event.entity.EntityPickupItemEvent;
111import org.bukkit.event.entity.EntityPlaceEvent;
112import org.bukkit.event.entity.EntityPotionEffectEvent;
113import org.bukkit.event.entity.PlayerDeathEvent;
114import org.bukkit.event.hanging.HangingBreakByEntityEvent;
115import org.bukkit.event.hanging.HangingPlaceEvent;
116import org.bukkit.event.inventory.ClickType;
117import org.bukkit.event.inventory.InventoryClickEvent;
118import org.bukkit.event.inventory.InventoryCloseEvent;
119import org.bukkit.event.player.AsyncPlayerChatEvent;
120import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
121import org.bukkit.event.player.PlayerBucketEmptyEvent;
122import org.bukkit.event.player.PlayerBucketFillEvent;
123import org.bukkit.event.player.PlayerChangedWorldEvent;
124import org.bukkit.event.player.PlayerCommandPreprocessEvent;
125import org.bukkit.event.player.PlayerDropItemEvent;
126import org.bukkit.event.player.PlayerEvent;
127import org.bukkit.event.player.PlayerInteractAtEntityEvent;
128import org.bukkit.event.player.PlayerInteractEntityEvent;
129import org.bukkit.event.player.PlayerInteractEvent;
130import org.bukkit.event.player.PlayerJoinEvent;
131import org.bukkit.event.player.PlayerLocaleChangeEvent;
132import org.bukkit.event.player.PlayerMoveEvent;
133import org.bukkit.event.player.PlayerPortalEvent;
134import org.bukkit.event.player.PlayerQuitEvent;
135import org.bukkit.event.player.PlayerRespawnEvent;
136import org.bukkit.event.player.PlayerTakeLecternBookEvent;
137import org.bukkit.event.player.PlayerTeleportEvent;
138import org.bukkit.event.vehicle.VehicleDestroyEvent;
139import org.bukkit.event.vehicle.VehicleEntityCollisionEvent;
140import org.bukkit.event.vehicle.VehicleMoveEvent;
141import org.bukkit.event.world.PortalCreateEvent;
142import org.bukkit.help.HelpTopic;
143import org.bukkit.inventory.ItemStack;
144import org.bukkit.inventory.PlayerInventory;
145import org.bukkit.inventory.meta.ItemMeta;
146import org.bukkit.metadata.FixedMetadataValue;
147import org.bukkit.metadata.MetadataValue;
148import org.bukkit.plugin.Plugin;
149import org.bukkit.potion.PotionEffect;
150import org.bukkit.util.Vector;
151import org.checkerframework.checker.nullness.qual.NonNull;
152
153import java.lang.reflect.Field;
154import java.util.HashSet;
155import java.util.List;
156import java.util.Locale;
157import java.util.Set;
158import java.util.UUID;
159
160/**
161 * Player Events involving plots.
162 */
163@SuppressWarnings("unused")
164public class PlayerEventListener implements Listener {
165
166    private static final Set<Material> MINECARTS = Set.of(
167            Material.MINECART,
168            Material.TNT_MINECART,
169            Material.CHEST_MINECART,
170            Material.COMMAND_BLOCK_MINECART,
171            Material.FURNACE_MINECART,
172            Material.HOPPER_MINECART
173    );
174    private static final Set<Material> BOOKS = Set.of(
175            Material.BOOK,
176            Material.KNOWLEDGE_BOOK,
177            Material.WRITABLE_BOOK,
178            Material.WRITTEN_BOOK
179    );
180    private static final Set<String> DYES;
181    static {
182        Set<String> mutableDyes = new HashSet<>(Set.of(
183                "WHITE_DYE",
184                "LIGHT_GRAY_DYE",
185                "GRAY_DYE",
186                "BLACK_DYE",
187                "BROWN_DYE",
188                "RED_DYE",
189                "ORANGE_DYE",
190                "YELLOW_DYE",
191                "LIME_DYE",
192                "GREEN_DYE",
193                "CYAN_DYE",
194                "LIGHT_BLUE_DYE",
195                "BLUE_DYE",
196                "PURPLE_DYE",
197                "MAGENTA_DYE",
198                "PINK_DYE",
199                "GLOW_INK_SAC"
200        ));
201        int[] version = PlotSquared.platform().serverVersion();
202        if (version[1] >= 20 && version[2] >= 1) {
203            mutableDyes.add("HONEYCOMB");
204        }
205        DYES = Set.copyOf(mutableDyes);
206    }
207    private final EventDispatcher eventDispatcher;
208    private final WorldEdit worldEdit;
209    private final PlotAreaManager plotAreaManager;
210    private final PlotListener plotListener;
211    // To prevent recursion
212    private boolean tmpTeleport = true;
213    private Field fieldPlayer;
214    private PlayerMoveEvent moveTmp;
215    private String internalVersion;
216
217    {
218        try {
219            fieldPlayer = PlayerEvent.class.getDeclaredField("player");
220            fieldPlayer.setAccessible(true);
221        } catch (NoSuchFieldException e) {
222            e.printStackTrace();
223        }
224    }
225
226    @Inject
227    public PlayerEventListener(
228            final @NonNull PlotAreaManager plotAreaManager,
229            final @NonNull EventDispatcher eventDispatcher,
230            final @NonNull WorldEdit worldEdit,
231            final @NonNull PlotListener plotListener
232    ) {
233        this.eventDispatcher = eventDispatcher;
234        this.worldEdit = worldEdit;
235        this.plotAreaManager = plotAreaManager;
236        this.plotListener = plotListener;
237    }
238
239    @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
240    public void onPlayerDyeSign(PlayerInteractEvent event) {
241        ItemStack itemStack = event.getItem();
242        if (itemStack == null) {
243            return;
244        }
245        Block block = event.getClickedBlock();
246        if (block != null && block.getState() instanceof Sign) {
247            if (DYES.contains(itemStack.getType().toString())) {
248                Location location = BukkitUtil.adapt(block.getLocation());
249                PlotArea area = location.getPlotArea();
250                if (area == null) {
251                    return;
252                }
253                Plot plot = location.getOwnedPlot();
254                if (plot == null) {
255                    if (PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, EditSignFlag.class, false)
256                            && !event.getPlayer().hasPermission(Permission.PERMISSION_ADMIN_INTERACT_ROAD.toString())) {
257                        event.setCancelled(true);
258                    }
259                    return;
260                }
261                if (plot.isAdded(event.getPlayer().getUniqueId())) {
262                    return; // allow for added players
263                }
264                if (!plot.getFlag(EditSignFlag.class)
265                        && !event.getPlayer().hasPermission(Permission.PERMISSION_ADMIN_INTERACT_OTHER.toString())) {
266                    plot.debug(event.getPlayer().getName() + " could not color the sign because of edit-sign = false");
267                    event.setCancelled(true);
268                }
269            }
270        }
271    }
272
273    @EventHandler(ignoreCancelled = true)
274    public void onEffect(@NonNull EntityPotionEffectEvent event) {
275        if (Settings.Enabled_Components.DISABLE_BEACON_EFFECT_OVERFLOW ||
276                event.getCause() != EntityPotionEffectEvent.Cause.BEACON ||
277                !(event.getEntity() instanceof Player player)) {
278            return;
279        }
280
281        UUID uuid = player.getUniqueId();
282        PotionEffect effect = event.getNewEffect();
283        if (effect == null) {
284            PotionEffect oldEffect = event.getOldEffect();
285            if (oldEffect != null) {
286                String name = oldEffect.getType().getName();
287                plotListener.addEffect(uuid, name, -1);
288            }
289        } else {
290            long expiresAt = System.currentTimeMillis() + effect.getDuration() * 50L; //Convert ticks to milliseconds
291            String name = effect.getType().getName();
292            plotListener.addEffect(uuid, name, expiresAt);
293        }
294    }
295
296    @EventHandler
297    public void onVehicleEntityCollision(VehicleEntityCollisionEvent e) {
298        if (e.getVehicle().getType() == EntityType.BOAT) {
299            Location location = BukkitUtil.adapt(e.getEntity().getLocation());
300            if (location.isPlotArea()) {
301                if (e.getEntity() instanceof Player) {
302                    PlotPlayer<Player> player = BukkitUtil.adapt((Player) e.getEntity());
303                    Plot plot = player.getCurrentPlot();
304                    if (plot != null) {
305                        if (!plot.isAdded(player.getUUID())) {
306                            //Here the event is only canceled if the player is not the owner
307                            //of the property on which he is located.
308                            e.setCancelled(true);
309                        }
310                    } else {
311                        e.setCancelled(true);
312                    }
313                } else {
314                    //Here the event is cancelled too, otherwise you can move the
315                    //boat with EchoPets or other mobs running around on the plot.
316                    e.setCancelled(true);
317                }
318            }
319        }
320    }
321
322    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
323    public void playerCommand(PlayerCommandPreprocessEvent event) {
324        String msg = event.getMessage().replace("/", "").toLowerCase(Locale.ROOT).trim();
325        if (msg.isEmpty()) {
326            return;
327        }
328        Player player = event.getPlayer();
329        PlotPlayer<Player> plotPlayer = BukkitUtil.adapt(player);
330        Location location = plotPlayer.getLocation();
331        PlotArea area = location.getPlotArea();
332        if (area == null) {
333            return;
334        }
335        String[] parts = msg.split(" ");
336        Plot plot = plotPlayer.getCurrentPlot();
337        // Check WorldEdit
338        switch (parts[0]) {
339            case "up", "worldedit:up" -> {
340                if (plot == null || (!plot.isAdded(plotPlayer.getUUID()) && !plotPlayer.hasPermission(
341                        Permission.PERMISSION_ADMIN_BUILD_OTHER,
342                        true
343                ))) {
344                    event.setCancelled(true);
345                    return;
346                }
347            }
348        }
349        if (plot == null && !area.isRoadFlags()) {
350            return;
351        }
352
353        List<String> blockedCommands = plot != null ?
354                plot.getFlag(BlockedCmdsFlag.class) :
355                area.getFlag(BlockedCmdsFlag.class);
356        if (blockedCommands.isEmpty()) {
357            return;
358        }
359        if (plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_INTERACT_BLOCKED_CMDS)) {
360            return;
361        }
362        // When using namespaced commands, we're not interested in the namespace
363        String part = parts[0];
364        if (part.contains(":")) {
365            String[] namespaced = part.split(":");
366            part = namespaced[1];
367            msg = msg.substring(namespaced[0].length() + 1);
368        }
369        msg = replaceAliases(msg, part);
370        for (String blocked : blockedCommands) {
371            if (blocked.equalsIgnoreCase(msg)) {
372                String perm;
373                if (plot != null && plot.isAdded(plotPlayer.getUUID())) {
374                    perm = "plots.admin.command.blocked-cmds.shared";
375                } else {
376                    perm = "plots.admin.command.blocked-cmds.road";
377                }
378                if (!plotPlayer.hasPermission(perm)) {
379                    plotPlayer.sendMessage(TranslatableCaption.of("blockedcmds.command_blocked"));
380                    event.setCancelled(true);
381                }
382                return;
383            }
384        }
385    }
386
387    private String replaceAliases(String msg, String part) {
388        String s1 = part;
389        Set<String> aliases = new HashSet<>();
390        for (HelpTopic cmdLabel : Bukkit.getServer().getHelpMap().getHelpTopics()) {
391            if (part.equals(cmdLabel.getName())) {
392                break;
393            }
394            String label = cmdLabel.getName().replaceFirst("/", "");
395            if (aliases.contains(label)) {
396                continue;
397            }
398            PluginCommand p = Bukkit.getPluginCommand(label);
399            if (p != null) {
400                for (String a : p.getAliases()) {
401                    if (aliases.contains(a)) {
402                        continue;
403                    }
404                    aliases.add(a);
405                    a = a.replaceFirst("/", "");
406                    if (!a.equals(label) && a.equals(part)) {
407                        part = label;
408                        break;
409                    }
410                }
411            }
412        }
413        if (!s1.equals(part)) {
414            msg = msg.replace(s1, part);
415        }
416        return msg;
417    }
418
419    @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
420    public void onPreLogin(final AsyncPlayerPreLoginEvent event) {
421        final UUID uuid;
422        if (Settings.UUID.OFFLINE) {
423            if (Settings.UUID.FORCE_LOWERCASE) {
424                uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + event.getName().toLowerCase()).getBytes(Charsets.UTF_8));
425            } else {
426                uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + event.getName()).getBytes(Charsets.UTF_8));
427            }
428        } else {
429            uuid = event.getUniqueId();
430        }
431        PlotSquared.get().getImpromptuUUIDPipeline().storeImmediately(event.getName(), uuid);
432    }
433
434    @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
435    @SuppressWarnings("deprecation") // Paper deprecation
436    public void onConnect(PlayerJoinEvent event) {
437        final Player player = event.getPlayer();
438        PlotSquared.platform().playerManager().removePlayer(player.getUniqueId());
439        final PlotPlayer<Player> pp = BukkitUtil.adapt(player);
440
441        // we're stripping the country code as we don't want to differ between countries
442        pp.setLocale(Locale.forLanguageTag(player.getLocale().substring(0, 2)));
443
444        Location location = pp.getLocation();
445        PlotArea area = location.getPlotArea();
446        if (area != null) {
447            Plot plot = area.getPlot(location);
448            if (plot != null) {
449                plotListener.plotEntry(pp, plot);
450            }
451        }
452        // Delayed
453
454        // Async
455        TaskManager.runTaskLaterAsync(() -> {
456            if (!player.hasPlayedBefore() && player.isOnline()) {
457                player.saveData();
458            }
459            this.eventDispatcher.doJoinTask(pp);
460        }, TaskTime.seconds(1L));
461
462        if (pp.hasPermission(Permission.PERMISSION_ADMIN_UPDATE_NOTIFICATION.toString()) && Settings.Enabled_Components.UPDATE_NOTIFICATIONS
463                && PremiumVerification.isPremium() && UpdateUtility.hasUpdate) {
464            Caption boundary = TranslatableCaption.of("update.update_boundary");
465            Caption updateNotification = TranslatableCaption.of("update.update_notification");
466            pp.sendMessage(boundary);
467            pp.sendMessage(
468                    updateNotification,
469                    TagResolver.builder()
470                            .tag("p2version", Tag.inserting(Component.text(UpdateUtility.internalVersion.versionString())))
471                            .tag("spigotversion", Tag.inserting(Component.text(UpdateUtility.spigotVersion)))
472                            .tag("downloadurl", Tag.preProcessParsed("https://www.spigotmc.org/resources/77506/updates"))
473                            .build()
474            );
475            pp.sendMessage(boundary);
476        }
477    }
478
479    @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
480    public void playerRespawn(PlayerRespawnEvent event) {
481        Player player = event.getPlayer();
482        PlotPlayer<Player> pp = BukkitUtil.adapt(player);
483        this.eventDispatcher.doRespawnTask(pp);
484    }
485
486    @SuppressWarnings("deprecation") // We explicitly want #getHomeSynchronous here
487    @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
488    public void onTeleport(PlayerTeleportEvent event) {
489        Player player = event.getPlayer();
490        //We need to account for bad plugins like NoCheatPlus that teleports player on/before login -_-
491        if (!player.isOnline()) {
492            return;
493        }
494        BukkitPlayer pp = BukkitUtil.adapt(player);
495        try (final MetaDataAccess<Plot> lastPlotAccess =
496                     pp.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LAST_PLOT)) {
497            Plot lastPlot = lastPlotAccess.get().orElse(null);
498            org.bukkit.Location to = event.getTo();
499            //noinspection ConstantConditions
500            if (to != null) {
501                Location location = BukkitUtil.adapt(to);
502                PlotArea area = location.getPlotArea();
503                if (area == null) {
504                    if (lastPlot != null) {
505                        plotListener.plotExit(pp, lastPlot);
506                        lastPlotAccess.remove();
507                    }
508                    try (final MetaDataAccess<Location> lastLocationAccess =
509                                 pp.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LOCATION)) {
510                        lastLocationAccess.remove();
511                    }
512                    return;
513                }
514                Plot plot = area.getPlot(location);
515                if (plot != null) {
516                    final boolean result = DenyTeleportFlag.allowsTeleport(pp, plot);
517                    // there is one possibility to still allow teleportation:
518                    // to is identical to the plot's home location, and untrusted-visit is true
519                    // i.e. untrusted-visit can override deny-teleport
520                    // this is acceptable, because otherwise it wouldn't make sense to have both flags set
521                    if (!result && !(plot.getFlag(UntrustedVisitFlag.class) && plot
522                            .getHomeSynchronous()
523                            .equals(BukkitUtil.adaptComplete(to)))) {
524                        pp.sendMessage(
525                                TranslatableCaption.of("deny.no_enter"),
526                                TagResolver.resolver("plot", Tag.inserting(Component.text(plot.toString())))
527                        );
528                        event.setCancelled(true);
529                    }
530                }
531            }
532        }
533        playerMove(event);
534    }
535
536    @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
537    public void vehicleMove(VehicleMoveEvent event)
538            throws IllegalAccessException {
539        final org.bukkit.Location from = event.getFrom();
540        final org.bukkit.Location to = event.getTo();
541
542        int toX, toZ;
543        if ((toX = MathMan.roundInt(to.getX())) != MathMan.roundInt(from.getX()) | (toZ = MathMan.roundInt(to.getZ())) != MathMan
544                .roundInt(from.getZ())) {
545            Vehicle vehicle = event.getVehicle();
546
547            // Check allowed
548            if (!vehicle.getPassengers().isEmpty()) {
549                Entity passenger = vehicle.getPassengers().get(0);
550
551                if (passenger instanceof final Player player) {
552                    // reset
553                    if (moveTmp == null) {
554                        moveTmp = new PlayerMoveEvent(null, from, to);
555                    }
556                    moveTmp.setFrom(from);
557                    moveTmp.setTo(to);
558                    moveTmp.setCancelled(false);
559                    fieldPlayer.set(moveTmp, player);
560
561                    List<Entity> passengers = vehicle.getPassengers();
562
563                    this.playerMove(moveTmp);
564                    org.bukkit.Location dest;
565                    if (moveTmp.isCancelled()) {
566                        dest = from;
567                    } else if (MathMan.roundInt(moveTmp.getTo().getX()) != toX || MathMan.roundInt(moveTmp
568                            .getTo()
569                            .getZ()) != toZ) {
570                        dest = to;
571                    } else {
572                        dest = null;
573                    }
574                    if (dest != null) {
575                        vehicle.eject();
576                        vehicle.setVelocity(new Vector(0d, 0d, 0d));
577                        PaperLib.teleportAsync(vehicle, dest);
578                        passengers.forEach(vehicle::addPassenger);
579                        return;
580                    }
581                }
582                if (Settings.Enabled_Components.KILL_ROAD_VEHICLES) {
583                    final com.sk89q.worldedit.world.entity.EntityType entityType = BukkitAdapter.adapt(vehicle.getType());
584                    // Horses etc are vehicles, but they're also animals
585                    // so this filters out all living entities
586                    if (EntityCategories.VEHICLE.contains(entityType) && !EntityCategories.ANIMAL.contains(entityType)) {
587                        List<MetadataValue> meta = vehicle.getMetadata("plot");
588                        Plot toPlot = BukkitUtil.adapt(to).getPlot();
589                        if (!meta.isEmpty()) {
590                            Plot origin = (Plot) meta.get(0).value();
591                            if (origin != null && !origin.getBasePlot(false).equals(toPlot)) {
592                                vehicle.remove();
593                            }
594                        } else if (toPlot != null) {
595                            vehicle.setMetadata("plot", new FixedMetadataValue((Plugin) PlotSquared.platform(), toPlot));
596                        }
597                    }
598                }
599            }
600
601        }
602    }
603
604    @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
605    public void playerMove(PlayerMoveEvent event) {
606        org.bukkit.Location from = event.getFrom();
607        org.bukkit.Location to = event.getTo();
608        int x2;
609        if (MathMan.roundInt(from.getX()) != (x2 = MathMan.roundInt(to.getX()))) {
610            Player player = event.getPlayer();
611            BukkitPlayer pp = BukkitUtil.adapt(player);
612            // Cancel teleport
613            if (TaskManager.removeFromTeleportQueue(pp.getName())) {
614                pp.sendMessage(TranslatableCaption.of("teleport.teleport_failed"));
615            }
616            // Set last location
617            Location location = BukkitUtil.adapt(to);
618            try (final MetaDataAccess<Location> lastLocationAccess =
619                         pp.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LOCATION)) {
620                lastLocationAccess.remove();
621            }
622            PlotArea area = location.getPlotArea();
623            if (area == null) {
624                try (final MetaDataAccess<Plot> lastPlotAccess =
625                             pp.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LAST_PLOT)) {
626                    lastPlotAccess.remove();
627                }
628                return;
629            }
630            Plot now = area.getPlot(location);
631            Plot lastPlot;
632            try (final MetaDataAccess<Plot> lastPlotAccess =
633                         pp.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LAST_PLOT)) {
634                lastPlot = lastPlotAccess.get().orElse(null);
635            }
636            if (now == null) {
637                try (final MetaDataAccess<Boolean> kickAccess =
638                             pp.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_KICK)) {
639                    if (lastPlot != null && !plotListener.plotExit(pp, lastPlot) && this.tmpTeleport && !kickAccess.get().orElse(
640                            false)) {
641                        pp.sendMessage(
642                                TranslatableCaption.of("permission.no_permission_event"),
643                                TagResolver.resolver(
644                                        "node",
645                                        Tag.inserting(Permission.PERMISSION_ADMIN_EXIT_DENIED)
646                                )
647                        );
648                        this.tmpTeleport = false;
649                        if (lastPlot.equals(BukkitUtil.adapt(from).getPlot())) {
650                            player.teleport(from);
651                        } else {
652                            player.teleport(player.getWorld().getSpawnLocation());
653                        }
654                        this.tmpTeleport = true;
655                        event.setCancelled(true);
656                        return;
657                    }
658                }
659            } else if (now.equals(lastPlot)) {
660                ForceFieldListener.handleForcefield(player, pp, now);
661            } else if (!plotListener.plotEntry(pp, now) && this.tmpTeleport) {
662                pp.sendMessage(
663                        TranslatableCaption.of("deny.no_enter"),
664                        TagResolver.resolver("plot", Tag.inserting(Component.text(now.toString())))
665                );
666                this.tmpTeleport = false;
667                to.setX(from.getBlockX());
668                to.setY(from.getBlockY());
669                to.setZ(from.getBlockZ());
670                player.teleport(event.getTo());
671                this.tmpTeleport = true;
672                return;
673            }
674            int border = area.getBorder(true);
675            int x1;
676            if (x2 > border && this.tmpTeleport) {
677                if (!pp.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_BORDER)) {
678                    to.setX(border - 1);
679                    this.tmpTeleport = false;
680                    player.teleport(event.getTo());
681                    this.tmpTeleport = true;
682                    pp.sendMessage(TranslatableCaption.of("border.denied"));
683                } else if (MathMan.roundInt(from.getX()) <= border) { // Only send if they just moved out of the border
684                    pp.sendMessage(TranslatableCaption.of("border.bypass.exited"));
685                }
686            } else if (x2 < -border && this.tmpTeleport) {
687                if (!pp.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_BORDER)) {
688                    to.setX(-border + 1);
689                    this.tmpTeleport = false;
690                    player.teleport(event.getTo());
691                    this.tmpTeleport = true;
692                    pp.sendMessage(TranslatableCaption.of("border.denied"));
693                } else if (MathMan.roundInt(from.getX()) >= -border) { // Only send if they just moved out of the border
694                    pp.sendMessage(TranslatableCaption.of("border.bypass.exited"));
695                }
696            } else if (((x1 = MathMan.roundInt(from.getX())) >= border && x2 <= border) || (x1 <= -border && x2 >= -border)) {
697                if (pp.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_BORDER)) {
698                    pp.sendMessage(TranslatableCaption.of("border.bypass.entered"));
699                }
700            }
701        }
702        int z2;
703        if (MathMan.roundInt(from.getZ()) != (z2 = MathMan.roundInt(to.getZ()))) {
704            Player player = event.getPlayer();
705            BukkitPlayer pp = BukkitUtil.adapt(player);
706            // Cancel teleport
707            if (TaskManager.removeFromTeleportQueue(pp.getName())) {
708                pp.sendMessage(TranslatableCaption.of("teleport.teleport_failed"));
709            }
710            // Set last location
711            Location location = BukkitUtil.adapt(to);
712            try (final MetaDataAccess<Location> lastLocationAccess =
713                         pp.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LOCATION)) {
714                lastLocationAccess.set(location);
715            }
716            PlotArea area = location.getPlotArea();
717            if (area == null) {
718                try (final MetaDataAccess<Plot> lastPlotAccess =
719                             pp.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LAST_PLOT)) {
720                    lastPlotAccess.remove();
721                }
722                return;
723            }
724            Plot plot = area.getPlot(location);
725            Plot lastPlot;
726            try (final MetaDataAccess<Plot> lastPlotAccess =
727                         pp.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LAST_PLOT)) {
728                lastPlot = lastPlotAccess.get().orElse(null);
729            }
730            if (plot == null) {
731                try (final MetaDataAccess<Boolean> kickAccess =
732                             pp.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_KICK)) {
733                    if (lastPlot != null && !plotListener.plotExit(pp, lastPlot) && this.tmpTeleport && !kickAccess.get().orElse(
734                            false)) {
735                        pp.sendMessage(
736                                TranslatableCaption.of("permission.no_permission_event"),
737                                TagResolver.resolver(
738                                        "node",
739                                        Tag.inserting(Permission.PERMISSION_ADMIN_EXIT_DENIED)
740                                )
741                        );
742                        this.tmpTeleport = false;
743                        if (lastPlot.equals(BukkitUtil.adapt(from).getPlot())) {
744                            player.teleport(from);
745                        } else {
746                            player.teleport(player.getWorld().getSpawnLocation());
747                        }
748                        this.tmpTeleport = true;
749                        event.setCancelled(true);
750                        return;
751                    }
752                }
753            } else if (plot.equals(lastPlot)) {
754                ForceFieldListener.handleForcefield(player, pp, plot);
755            } else if (!plotListener.plotEntry(pp, plot) && this.tmpTeleport) {
756                pp.sendMessage(
757                        TranslatableCaption.of("deny.no_enter"),
758                        TagResolver.resolver("plot", Tag.inserting(Component.text(plot.toString())))
759                );
760                this.tmpTeleport = false;
761                player.teleport(from);
762                to.setX(from.getBlockX());
763                to.setY(from.getBlockY());
764                to.setZ(from.getBlockZ());
765                player.teleport(event.getTo());
766                this.tmpTeleport = true;
767                return;
768            }
769            int border = area.getBorder(true);
770            int z1;
771            if (z2 > border && this.tmpTeleport) {
772                if (!pp.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_BORDER)) {
773                    to.setZ(border - 1);
774                    this.tmpTeleport = false;
775                    player.teleport(event.getTo());
776                    this.tmpTeleport = true;
777                    pp.sendMessage(TranslatableCaption.of("border.denied"));
778                } else if (MathMan.roundInt(from.getZ()) <= border) { // Only send if they just moved out of the border
779                    pp.sendMessage(TranslatableCaption.of("border.bypass.exited"));
780                }
781            } else if (z2 < -border && this.tmpTeleport) {
782                if (!pp.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_BORDER)) {
783                    to.setZ(-border + 1);
784                    this.tmpTeleport = false;
785                    player.teleport(event.getTo());
786                    this.tmpTeleport = true;
787                    pp.sendMessage(TranslatableCaption.of("border.denied"));
788                } else if (MathMan.roundInt(from.getZ()) >= -border) { // Only send if they just moved out of the border
789                    pp.sendMessage(TranslatableCaption.of("border.bypass.exited"));
790                }
791            } else if (((z1 = MathMan.roundInt(from.getZ())) >= border && z2 <= border) || (z1 <= -border && z2 >= -border)) {
792                if (pp.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_BORDER)) {
793                    pp.sendMessage(TranslatableCaption.of("border.bypass.entered"));
794                }
795            }
796        }
797    }
798
799    @EventHandler(priority = EventPriority.LOW)
800    @SuppressWarnings("deprecation") // Paper deprecation
801    public void onChat(AsyncPlayerChatEvent event) {
802        if (event.isCancelled()) {
803            return;
804        }
805
806        BukkitPlayer plotPlayer = BukkitUtil.adapt(event.getPlayer());
807        Location location = plotPlayer.getLocation();
808        PlotArea area = location.getPlotArea();
809        if (area == null) {
810            return;
811        }
812        Plot plot = area.getPlot(location);
813        if (plot == null) {
814            return;
815        }
816        if (!((plot.getFlag(ChatFlag.class) && area.isPlotChat() && plotPlayer.getAttribute("chat"))
817                || area.isForcingPlotChat())) {
818            return;
819        }
820        if (plot.isDenied(plotPlayer.getUUID()) && !plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_CHAT_BYPASS)) {
821            return;
822        }
823        event.setCancelled(true);
824        Set<Player> recipients = event.getRecipients();
825        recipients.clear();
826        Set<PlotPlayer<?>> spies = new HashSet<>();
827        Set<PlotPlayer<?>> plotRecipients = new HashSet<>();
828        for (final PlotPlayer<?> pp : PlotSquared.platform().playerManager().getPlayers()) {
829            if (pp.getAttribute("chatspy")) {
830                spies.add(pp);
831            } else {
832                Plot current = pp.getCurrentPlot();
833                if (current != null && current.getBasePlot(false).equals(plot)) {
834                    plotRecipients.add(pp);
835                }
836            }
837        }
838        String message = event.getMessage();
839        String sender = event.getPlayer().getDisplayName();
840        PlotId id = plot.getId();
841        String worldName = plot.getWorldName();
842        Caption msg = TranslatableCaption.of("chat.plot_chat_format");
843        TagResolver.Builder builder = TagResolver.builder();
844        builder.tag("world", Tag.inserting(Component.text(worldName)));
845        builder.tag("plot_id", Tag.inserting(Component.text(id.toString())));
846        builder.tag("sender", Tag.inserting(Component.text(sender)));
847        if (plotPlayer.hasPermission("plots.chat.color")) {
848            builder.tag("msg", Tag.inserting(MiniMessage.miniMessage().deserialize(
849                    message,
850                    TagResolver.resolver(StandardTags.color(), StandardTags.gradient(),
851                            StandardTags.rainbow(), StandardTags.decorations()
852                    )
853            )));
854        } else {
855            builder.tag("msg", Tag.inserting(Component.text(message)));
856        }
857        for (PlotPlayer<?> receiver : plotRecipients) {
858            receiver.sendMessage(msg, builder.build());
859        }
860        if (!spies.isEmpty()) {
861            Caption spymsg = TranslatableCaption.of("chat.plot_chat_spy_format");
862            for (PlotPlayer<?> player : spies) {
863                player.sendMessage(spymsg, builder.tag("message", Tag.inserting(Component.text(message))).build());
864            }
865        }
866        if (Settings.Chat.LOG_PLOTCHAT_TO_CONSOLE) {
867            Caption spymsg = TranslatableCaption.of("chat.plot_chat_spy_format");
868            ConsolePlayer.getConsole().sendMessage(
869                    spymsg,
870                    builder.tag("message", Tag.inserting(Component.text(message))).build()
871            );
872        }
873    }
874
875    @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
876    public void onWorldChanged(PlayerChangedWorldEvent event) {
877        Player player = event.getPlayer();
878        BukkitPlayer pp = BukkitUtil.adapt(player);
879        // Delete last location
880        Plot plot;
881        try (final MetaDataAccess<Plot> lastPlotAccess =
882                     pp.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LAST_PLOT)) {
883            plot = lastPlotAccess.remove();
884        }
885        try (final MetaDataAccess<Location> lastLocationAccess =
886                     pp.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LOCATION)) {
887            lastLocationAccess.remove();
888        }
889        if (plot != null) {
890            plotListener.plotExit(pp, plot);
891        }
892        if (this.worldEdit != null) {
893            if (!pp.hasPermission(Permission.PERMISSION_WORLDEDIT_BYPASS)) {
894                if (pp.getAttribute("worldedit")) {
895                    pp.removeAttribute("worldedit");
896                }
897            }
898        }
899        Location location = pp.getLocation();
900        PlotArea area = location.getPlotArea();
901        if (location.isPlotArea()) {
902            plot = location.getPlot();
903            if (plot != null) {
904                plotListener.plotEntry(pp, plot);
905            }
906        }
907    }
908
909    @SuppressWarnings("deprecation")
910    @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
911    public void onInventoryClick(InventoryClickEvent event) {
912        /*if (!event.isLeftClick() || (event.getAction() != InventoryAction.PLACE_ALL) || event
913            .isShiftClick()) {
914            return;
915        }*/
916        HumanEntity entity = event.getWhoClicked();
917        if (!(entity instanceof Player) || !this.plotAreaManager
918                .hasPlotArea(entity.getWorld().getName())) {
919            return;
920        }
921
922        HumanEntity clicker = event.getWhoClicked();
923        if (!(clicker instanceof Player player)) {
924            return;
925        }
926        BukkitPlayer pp = BukkitUtil.adapt(player);
927        final PlotInventory inventory = PlotInventory.getOpenPlotInventory(pp);
928        if (inventory != null && event.getRawSlot() == event.getSlot()) {
929            if (!inventory.onClick(event.getSlot())) {
930                event.setResult(Event.Result.DENY);
931                event.setCancelled(true);
932                inventory.close();
933            }
934        }
935        PlayerInventory inv = player.getInventory();
936        int slot = inv.getHeldItemSlot();
937        if ((slot > 8) || !event.getEventName().equals("InventoryCreativeEvent")) {
938            return;
939        }
940        ItemStack oldItem = inv.getItemInHand();
941        ItemMeta oldMeta = oldItem.getItemMeta();
942        ItemStack newItem = event.getCursor();
943        ItemMeta newMeta = newItem.getItemMeta();
944
945        if (event.getClick() == ClickType.CREATIVE) {
946            final Plot plot = pp.getCurrentPlot();
947            if (plot != null) {
948                if (plot.getFlag(PreventCreativeCopyFlag.class) && !plot
949                        .isAdded(player.getUniqueId()) && !pp.hasPermission(Permission.PERMISSION_ADMIN_INTERACT_OTHER)) {
950                    final ItemStack newStack =
951                            new ItemStack(newItem.getType(), newItem.getAmount());
952                    event.setCursor(newStack);
953                    plot.debug(player.getName()
954                            + " could not creative-copy an item because prevent-creative-copy = true");
955                }
956            } else {
957                PlotArea area = pp.getPlotAreaAbs();
958                if (area != null && PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, PreventCreativeCopyFlag.class, true)) {
959                    final ItemStack newStack =
960                            new ItemStack(newItem.getType(), newItem.getAmount());
961                    event.setCursor(newStack);
962                }
963            }
964            return;
965        }
966
967        String newLore = "";
968        if (newMeta != null) {
969            List<String> lore = newMeta.getLore();
970            if (lore != null) {
971                newLore = lore.toString();
972            }
973        }
974        String oldLore = "";
975        if (oldMeta != null) {
976            List<String> lore = oldMeta.getLore();
977            if (lore != null) {
978                oldLore = lore.toString();
979            }
980        }
981        Material itemType = newItem.getType();
982        if (!"[(+NBT)]".equals(newLore) || (oldItem.equals(newItem) && newLore.equals(oldLore))) {
983            if (newMeta == null || (itemType != Material.LEGACY_BANNER && itemType != Material.PLAYER_HEAD)) {
984                return;
985            }
986        }
987        Block block = player.getTargetBlock(null, 7);
988        org.bukkit.block.BlockState state = block.getState();
989        Material stateType = state.getType();
990        if (stateType != itemType) {
991            if (stateType == Material.LEGACY_WALL_BANNER || stateType == Material.LEGACY_STANDING_BANNER) {
992                if (itemType != Material.LEGACY_BANNER) {
993                    return;
994                }
995            } else if (stateType == Material.LEGACY_SKULL) {
996                if (itemType != Material.LEGACY_SKULL_ITEM) {
997                    return;
998                }
999            } else {
1000                return;
1001            }
1002        }
1003        Location location = BukkitUtil.adapt(state.getLocation());
1004        PlotArea area = location.getPlotArea();
1005        if (area == null) {
1006            return;
1007        }
1008        Plot plot = area.getPlotAbs(location);
1009        boolean cancelled = false;
1010        if (plot == null) {
1011            if (!pp.hasPermission(Permission.PERMISSION_ADMIN_INTERACT_ROAD)) {
1012                pp.sendMessage(
1013                        TranslatableCaption.of("permission.no_permission_event"),
1014                        TagResolver.resolver(
1015                                "node",
1016                                Tag.inserting(Permission.PERMISSION_ADMIN_INTERACT_ROAD)
1017                        )
1018                );
1019                cancelled = true;
1020            }
1021        } else if (!plot.hasOwner()) {
1022            if (!pp.hasPermission(Permission.PERMISSION_ADMIN_INTERACT_UNOWNED)) {
1023                pp.sendMessage(
1024                        TranslatableCaption.of("permission.no_permission_event"),
1025                        TagResolver.resolver(
1026                                "node",
1027                                Tag.inserting(Permission.PERMISSION_ADMIN_INTERACT_UNOWNED)
1028                        )
1029                );
1030                cancelled = true;
1031            }
1032        } else {
1033            UUID uuid = pp.getUUID();
1034            if (!plot.isAdded(uuid)) {
1035                if (!pp.hasPermission(Permission.PERMISSION_ADMIN_INTERACT_OTHER)) {
1036                    pp.sendMessage(
1037                            TranslatableCaption.of("permission.no_permission_event"),
1038                            TagResolver.resolver(
1039                                    "node",
1040                                    Tag.inserting(Permission.PERMISSION_ADMIN_INTERACT_OTHER)
1041                            )
1042                    );
1043                    cancelled = true;
1044                }
1045            }
1046        }
1047        if (cancelled) {
1048            if ((oldItem.getType() == newItem.getType()) && (oldItem.getDurability() == newItem
1049                    .getDurability())) {
1050                event.setCursor(
1051                        new ItemStack(newItem.getType(), newItem.getAmount(), newItem.getDurability()));
1052                event.setCancelled(true);
1053                return;
1054            }
1055            event.setCursor(
1056                    new ItemStack(newItem.getType(), newItem.getAmount(), newItem.getDurability()));
1057        }
1058    }
1059
1060    @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
1061    public void onInteract(PlayerInteractAtEntityEvent e) {
1062        Entity entity = e.getRightClicked();
1063        if (!(entity instanceof ArmorStand) && !(entity instanceof ItemFrame)) {
1064            return;
1065        }
1066        Location location = BukkitUtil.adapt(e.getRightClicked().getLocation());
1067        PlotArea area = location.getPlotArea();
1068        if (area == null) {
1069            return;
1070        }
1071        EntitySpawnListener.testNether(entity);
1072        Plot plot = location.getPlotAbs();
1073        BukkitPlayer pp = BukkitUtil.adapt(e.getPlayer());
1074        if (plot == null) {
1075            if (!PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, MiscInteractFlag.class, true) && !pp.hasPermission(
1076                    Permission.PERMISSION_ADMIN_INTERACT_ROAD
1077            )) {
1078                pp.sendMessage(
1079                        TranslatableCaption.of("permission.no_permission_event"),
1080                        TagResolver.resolver(
1081                                "node",
1082                                Tag.inserting(Permission.PERMISSION_ADMIN_INTERACT_ROAD)
1083                        )
1084                );
1085                e.setCancelled(true);
1086            }
1087        } else {
1088            if (Settings.Done.RESTRICT_BUILDING && DoneFlag.isDone(plot)) {
1089                if (!pp.hasPermission(Permission.PERMISSION_ADMIN_BUILD_OTHER)) {
1090                    pp.sendMessage(TranslatableCaption.of("done.building_restricted"));
1091                    e.setCancelled(true);
1092                    return;
1093                }
1094            }
1095            if (!plot.hasOwner()) {
1096                if (!pp.hasPermission("plots.admin.interact.unowned")) {
1097                    pp.sendMessage(
1098                            TranslatableCaption.of("permission.no_permission_event"),
1099                            TagResolver.resolver(
1100                                    "node",
1101                                    Tag.inserting(Permission.PERMISSION_ADMIN_INTERACT_UNOWNED)
1102                            )
1103                    );
1104                    e.setCancelled(true);
1105                }
1106            } else {
1107                UUID uuid = pp.getUUID();
1108                if (plot.isAdded(uuid)) {
1109                    return;
1110                }
1111                if (plot.getFlag(MiscInteractFlag.class)) {
1112                    return;
1113                }
1114                if (!pp.hasPermission(Permission.PERMISSION_ADMIN_INTERACT_OTHER)) {
1115                    pp.sendMessage(
1116                            TranslatableCaption.of("permission.no_permission_event"),
1117                            TagResolver.resolver(
1118                                    "node",
1119                                    Tag.inserting(Permission.PERMISSION_ADMIN_INTERACT_OTHER)
1120                            )
1121                    );
1122                    e.setCancelled(true);
1123                    plot.debug(pp.getName() + " could not interact with " + entity.getType()
1124                            + " because misc-interact = false");
1125                }
1126            }
1127        }
1128    }
1129
1130    @EventHandler(priority = EventPriority.LOW)
1131    @SuppressWarnings("deprecation") // Paper deprecation
1132    public void onCancelledInteract(PlayerInteractEvent event) {
1133        if (event.isCancelled() && event.getAction() == Action.RIGHT_CLICK_AIR) {
1134            Player player = event.getPlayer();
1135            BukkitPlayer pp = BukkitUtil.adapt(player);
1136            PlotArea area = pp.getPlotAreaAbs();
1137            if (area == null) {
1138                return;
1139            }
1140            if (event.getAction() == Action.RIGHT_CLICK_AIR) {
1141                Material item = event.getMaterial();
1142                if (item.toString().toLowerCase().endsWith("_egg")) {
1143                    event.setCancelled(true);
1144                    event.setUseItemInHand(Event.Result.DENY);
1145                }
1146            }
1147            ItemStack hand = player.getInventory().getItemInMainHand();
1148            ItemStack offHand = player.getInventory().getItemInOffHand();
1149            Material type = hand.getType();
1150            Material offType = offHand.getType();
1151            if (type == Material.AIR) {
1152                type = offType;
1153            }
1154            if (type.toString().toLowerCase().endsWith("_egg")) {
1155                Block block = player.getTargetBlockExact(5, FluidCollisionMode.SOURCE_ONLY);
1156                if (block != null && block.getType() != Material.AIR) {
1157                    Location location = BukkitUtil.adapt(block.getLocation());
1158                    if (!this.eventDispatcher.checkPlayerBlockEvent(pp, PlayerBlockEventType.SPAWN_MOB, location, null, true)) {
1159                        event.setCancelled(true);
1160                        event.setUseItemInHand(Event.Result.DENY);
1161                    }
1162                }
1163            }
1164        }
1165    }
1166
1167    @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
1168    public void onInteract(PlayerInteractEvent event) {
1169        Player player = event.getPlayer();
1170        BukkitPlayer pp = BukkitUtil.adapt(player);
1171        PlotArea area = pp.getPlotAreaAbs();
1172        if (area == null) {
1173            return;
1174        }
1175        PlayerBlockEventType eventType;
1176        BlockType blocktype1;
1177        Block block = event.getClickedBlock();
1178        if (block == null) {
1179            // We do not care in this case, the player is likely interacting with air ("nothing").
1180            return;
1181        }
1182        Location location = BukkitUtil.adapt(block.getLocation());
1183        Action action = event.getAction();
1184        switch (action) {
1185            case PHYSICAL -> {
1186                eventType = PlayerBlockEventType.TRIGGER_PHYSICAL;
1187                blocktype1 = BukkitAdapter.asBlockType(block.getType());
1188            }
1189
1190            //todo rearrange the right click code. it is all over the place.
1191            case RIGHT_CLICK_BLOCK -> {
1192                Material blockType = block.getType();
1193                eventType = PlayerBlockEventType.INTERACT_BLOCK;
1194                blocktype1 = BukkitAdapter.asBlockType(block.getType());
1195
1196                if (blockType.isInteractable()) {
1197                    if (!player.isSneaking()) {
1198                        break;
1199                    }
1200                    ItemStack hand = player.getInventory().getItemInMainHand();
1201                    ItemStack offHand = player.getInventory().getItemInOffHand();
1202
1203                    // sneaking players interact with blocks if both hands are empty
1204                    if (hand.getType() == Material.AIR && offHand.getType() == Material.AIR) {
1205                        break;
1206                    }
1207                }
1208
1209                Material type = event.getMaterial();
1210
1211                // in the following, lb needs to have the material of the item in hand i.e. type
1212                switch (type.toString()) {
1213                    case "REDSTONE", "STRING", "PUMPKIN_SEEDS", "MELON_SEEDS", "COCOA_BEANS", "WHEAT_SEEDS", "BEETROOT_SEEDS",
1214                            "SWEET_BERRIES", "GLOW_BERRIES" -> {
1215                        return;
1216                    }
1217                    default -> {
1218                        //eventType = PlayerBlockEventType.PLACE_BLOCK;
1219                        if (type.isBlock()) {
1220                            return;
1221                        }
1222                    }
1223                }
1224                if (PaperLib.isPaper()) {
1225                    if (MaterialTags.SPAWN_EGGS.isTagged(type) || Material.EGG.equals(type)) {
1226                        eventType = PlayerBlockEventType.SPAWN_MOB;
1227                        break;
1228                    }
1229                } else {
1230                    if (type.toString().toLowerCase().endsWith("egg")) {
1231                        eventType = PlayerBlockEventType.SPAWN_MOB;
1232                        break;
1233                    }
1234                }
1235                if (type.isEdible()) {
1236                    //Allow all players to eat while also allowing the block place event to be fired
1237                    return;
1238                }
1239                if (type == Material.ARMOR_STAND) {
1240                    location = BukkitUtil.adapt(block.getRelative(event.getBlockFace()).getLocation());
1241                    eventType = PlayerBlockEventType.PLACE_MISC;
1242                }
1243                if (org.bukkit.Tag.ITEMS_BOATS.isTagged(type) || MINECARTS.contains(type)) {
1244                    eventType = PlayerBlockEventType.PLACE_VEHICLE;
1245                    break;
1246                }
1247                if (type == Material.FIREWORK_ROCKET || type == Material.FIREWORK_STAR) {
1248                    eventType = PlayerBlockEventType.SPAWN_MOB;
1249                    break;
1250                }
1251                if (BOOKS.contains(type)) {
1252                    eventType = PlayerBlockEventType.READ;
1253                    break;
1254                }
1255            }
1256            case LEFT_CLICK_BLOCK -> {
1257                Material blockType = block.getType();
1258
1259                // todo: when the code above is rearranged, it would be great to beautify this as well.
1260                // will code this as a temporary, specific bug fix (for dragon eggs)
1261                if (blockType != Material.DRAGON_EGG) {
1262                    return;
1263                }
1264
1265                eventType = PlayerBlockEventType.INTERACT_BLOCK;
1266                blocktype1 = BukkitAdapter.asBlockType(block.getType());
1267            }
1268            default -> {
1269                return;
1270            }
1271        }
1272        if (this.worldEdit != null && pp.getAttribute("worldedit")) {
1273            if (event.getMaterial() == Material.getMaterial(this.worldEdit.getConfiguration().wandItem)) {
1274                return;
1275            }
1276        }
1277        if (!this.eventDispatcher.checkPlayerBlockEvent(pp, eventType, location, blocktype1, true)) {
1278            event.setCancelled(true);
1279            event.setUseInteractedBlock(Event.Result.DENY);
1280        }
1281    }
1282
1283    // Boats can sometimes be placed on interactable blocks such as levers,
1284    // see PS-175. Armor stands, minecarts and end crystals (the other entities
1285    // supported by this event) don't have this issue.
1286    @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
1287    public void onBoatPlace(EntityPlaceEvent event) {
1288        Player player = event.getPlayer();
1289        if (player == null) {
1290            return;
1291        }
1292        Entity placed = event.getEntity();
1293        if (!(placed instanceof Boat)) {
1294            return;
1295        }
1296        BukkitPlayer pp = BukkitUtil.adapt(event.getPlayer());
1297        PlotArea area = pp.getPlotAreaAbs();
1298        if (area == null) {
1299            return;
1300        }
1301        PlayerBlockEventType eventType = PlayerBlockEventType.PLACE_VEHICLE;
1302        Block block = event.getBlock();
1303        BlockType blockType = BukkitAdapter.asBlockType(block.getType());
1304        Location location = BukkitUtil.adapt(block.getLocation());
1305        if (!PlotSquared.get().getEventDispatcher()
1306                .checkPlayerBlockEvent(pp, eventType, location, blockType, true)) {
1307            event.setCancelled(true);
1308        }
1309    }
1310
1311    @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
1312    public void onBucketEmpty(PlayerBucketEmptyEvent event) {
1313        BlockFace bf = event.getBlockFace();
1314        // Note: a month after Bukkit 1.14.4 released, they added the API method
1315        // PlayerBucketEmptyEvent#getBlock(), which returns the block the
1316        // bucket contents is going to be placed at. Currently we determine this
1317        // block ourselves to retain compatibility with 1.13.
1318        final Block block;
1319        // if the block can be waterlogged, the event might waterlog the block
1320        // sometimes
1321        if (event.getBlockClicked().getBlockData() instanceof Waterlogged waterlogged
1322                && !waterlogged.isWaterlogged() && event.getBucket() != Material.LAVA_BUCKET) {
1323            block = event.getBlockClicked();
1324        } else {
1325            block = event.getBlockClicked().getLocation()
1326                    .add(bf.getModX(), bf.getModY(), bf.getModZ())
1327                    .getBlock();
1328        }
1329        Location location = BukkitUtil.adapt(block.getLocation());
1330        PlotArea area = location.getPlotArea();
1331        if (area == null) {
1332            return;
1333        }
1334        BukkitPlayer pp = BukkitUtil.adapt(event.getPlayer());
1335        Plot plot = area.getPlot(location);
1336        if (plot == null) {
1337            if (pp.hasPermission(Permission.PERMISSION_ADMIN_BUILD_ROAD)) {
1338                return;
1339            }
1340            pp.sendMessage(
1341                    TranslatableCaption.of("permission.no_permission_event"),
1342                    TagResolver.resolver("node", Tag.inserting(Permission.PERMISSION_ADMIN_BUILD_ROAD))
1343            );
1344            event.setCancelled(true);
1345        } else if (!plot.hasOwner()) {
1346            if (pp.hasPermission(Permission.PERMISSION_ADMIN_BUILD_UNOWNED)) {
1347                return;
1348            }
1349            pp.sendMessage(
1350                    TranslatableCaption.of("permission.no_permission_event"),
1351                    TagResolver.resolver(
1352                            "node",
1353                            Tag.inserting(Permission.PERMISSION_ADMIN_BUILD_UNOWNED)
1354                    )
1355            );
1356            event.setCancelled(true);
1357        } else if (!plot.isAdded(pp.getUUID())) {
1358            if (pp.hasPermission(Permission.PERMISSION_ADMIN_BUILD_OTHER)) {
1359                return;
1360            }
1361            pp.sendMessage(
1362                    TranslatableCaption.of("permission.no_permission_event"),
1363                    TagResolver.resolver(
1364                            "node",
1365                            Tag.inserting(Permission.PERMISSION_ADMIN_BUILD_OTHER)
1366                    )
1367            );
1368            event.setCancelled(true);
1369        } else if (Settings.Done.RESTRICT_BUILDING && DoneFlag.isDone(plot)) {
1370            if (!pp.hasPermission(Permission.PERMISSION_ADMIN_BUILD_OTHER)) {
1371                pp.sendMessage(
1372                        TranslatableCaption.of("done.building_restricted")
1373                );
1374                event.setCancelled(true);
1375            }
1376        }
1377    }
1378
1379    @EventHandler(priority = EventPriority.HIGHEST)
1380    public void onInventoryClose(InventoryCloseEvent event) {
1381        HumanEntity closer = event.getPlayer();
1382        if (!(closer instanceof Player player)) {
1383            return;
1384        }
1385        PlotInventory.removePlotInventoryOpen(BukkitUtil.adapt(player));
1386    }
1387
1388    @EventHandler(priority = EventPriority.MONITOR)
1389    public void onLeave(PlayerQuitEvent event) {
1390        TaskManager.removeFromTeleportQueue(event.getPlayer().getName());
1391        BukkitPlayer pp = BukkitUtil.adapt(event.getPlayer());
1392        pp.unregister();
1393        plotListener.logout(pp.getUUID());
1394    }
1395
1396    @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
1397    public void onBucketFill(PlayerBucketFillEvent event) {
1398        Block blockClicked = event.getBlockClicked();
1399        Location location = BukkitUtil.adapt(blockClicked.getLocation());
1400        PlotArea area = location.getPlotArea();
1401        if (area == null) {
1402            return;
1403        }
1404        Player player = event.getPlayer();
1405        BukkitPlayer plotPlayer = BukkitUtil.adapt(player);
1406        Plot plot = area.getPlot(location);
1407        if (plot == null) {
1408            if (plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_BUILD_ROAD)) {
1409                return;
1410            }
1411            plotPlayer.sendMessage(
1412                    TranslatableCaption.of("permission.no_permission_event"),
1413                    TagResolver.resolver("node", Tag.inserting(Permission.PERMISSION_ADMIN_BUILD_ROAD))
1414            );
1415            event.setCancelled(true);
1416        } else if (!plot.hasOwner()) {
1417            if (plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_BUILD_UNOWNED)) {
1418                return;
1419            }
1420            plotPlayer.sendMessage(
1421                    TranslatableCaption.of("permission.no_permission_event"),
1422                    TagResolver.resolver(
1423                            "node",
1424                            Tag.inserting(Permission.PERMISSION_ADMIN_BUILD_UNOWNED)
1425                    )
1426            );
1427            event.setCancelled(true);
1428        } else if (!plot.isAdded(plotPlayer.getUUID())) {
1429            if (plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_BUILD_OTHER)) {
1430                return;
1431            }
1432            plotPlayer.sendMessage(
1433                    TranslatableCaption.of("permission.no_permission_event"),
1434                    TagResolver.resolver(
1435                            "node",
1436                            Tag.inserting(Permission.PERMISSION_ADMIN_BUILD_OTHER)
1437                    )
1438            );
1439            event.setCancelled(true);
1440        } else if (Settings.Done.RESTRICT_BUILDING && DoneFlag.isDone(plot)) {
1441            if (!plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_BUILD_OTHER)) {
1442                plotPlayer.sendMessage(
1443                        TranslatableCaption.of("done.building_restricted")
1444                );
1445                event.setCancelled(true);
1446            }
1447        }
1448    }
1449
1450    @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
1451    public void onHangingPlace(HangingPlaceEvent event) {
1452        Block block = event.getBlock().getRelative(event.getBlockFace());
1453        Location location = BukkitUtil.adapt(block.getLocation());
1454        PlotArea area = location.getPlotArea();
1455        if (area == null) {
1456            return;
1457        }
1458        Player p = event.getPlayer();
1459        if (p == null) {
1460            event.setCancelled(true);
1461            return;
1462        }
1463        BukkitPlayer pp = BukkitUtil.adapt(p);
1464        Plot plot = area.getPlot(location);
1465        if (plot == null) {
1466            if (!pp.hasPermission(Permission.PERMISSION_ADMIN_BUILD_ROAD)) {
1467                pp.sendMessage(
1468                        TranslatableCaption.of("permission.no_permission_event"),
1469                        TagResolver.resolver(
1470                                "node",
1471                                Tag.inserting(Permission.PERMISSION_ADMIN_BUILD_ROAD)
1472                        )
1473                );
1474                event.setCancelled(true);
1475            }
1476        } else {
1477            if (!plot.hasOwner()) {
1478                if (!pp.hasPermission(Permission.PERMISSION_ADMIN_BUILD_UNOWNED)) {
1479                    pp.sendMessage(
1480                            TranslatableCaption.of("permission.no_permission_event"),
1481                            TagResolver.resolver(
1482                                    "node",
1483                                    Tag.inserting(Permission.PERMISSION_ADMIN_BUILD_UNOWNED)
1484                            )
1485                    );
1486                    event.setCancelled(true);
1487                }
1488                return;
1489            }
1490            if (!plot.isAdded(pp.getUUID())) {
1491                if (!plot.getFlag(HangingPlaceFlag.class)) {
1492                    if (!pp.hasPermission(Permission.PERMISSION_ADMIN_BUILD_OTHER)) {
1493                        pp.sendMessage(
1494                                TranslatableCaption.of("permission.no_permission_event"),
1495                                TagResolver.resolver(
1496                                        "node",
1497                                        Tag.inserting(Permission.PERMISSION_ADMIN_BUILD_OTHER)
1498                                )
1499                        );
1500                        event.setCancelled(true);
1501                    }
1502                    return;
1503                }
1504            }
1505            if (BukkitEntityUtil.checkEntity(event.getEntity(), plot)) {
1506                event.setCancelled(true);
1507            }
1508
1509        }
1510    }
1511
1512    @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
1513    public void onHangingBreakByEntity(HangingBreakByEntityEvent event) {
1514        Entity remover = event.getRemover();
1515        if (remover instanceof Player p) {
1516            Location location = BukkitUtil.adapt(event.getEntity().getLocation());
1517            PlotArea area = location.getPlotArea();
1518            if (area == null) {
1519                return;
1520            }
1521            BukkitPlayer pp = BukkitUtil.adapt(p);
1522            Plot plot = area.getPlot(location);
1523            if (plot == null) {
1524                if (!pp.hasPermission(Permission.PERMISSION_ADMIN_DESTROY_ROAD)) {
1525                    pp.sendMessage(
1526                            TranslatableCaption.of("permission.no_permission_event"),
1527                            TagResolver.resolver(
1528                                    "node",
1529                                    Tag.inserting(Permission.PERMISSION_ADMIN_DESTROY_ROAD)
1530                            )
1531                    );
1532                    event.setCancelled(true);
1533                }
1534            } else if (!plot.hasOwner()) {
1535                if (!pp.hasPermission(Permission.PERMISSION_ADMIN_DESTROY_UNOWNED)) {
1536                    pp.sendMessage(
1537                            TranslatableCaption.of("permission.no_permission_event"),
1538                            TagResolver.resolver(
1539                                    "node",
1540                                    Tag.inserting(Permission.PERMISSION_ADMIN_DESTROY_UNOWNED)
1541                            )
1542                    );
1543                    event.setCancelled(true);
1544                }
1545            } else if (!plot.isAdded(pp.getUUID())) {
1546                if (plot.getFlag(HangingBreakFlag.class)) {
1547                    return;
1548                }
1549                if (!pp.hasPermission(Permission.PERMISSION_ADMIN_DESTROY_OTHER)) {
1550                    pp.sendMessage(
1551                            TranslatableCaption.of("permission.no_permission_event"),
1552                            TagResolver.resolver(
1553                                    "node",
1554                                    Tag.inserting(Permission.PERMISSION_ADMIN_DESTROY_OTHER)
1555                            )
1556                    );
1557                    event.setCancelled(true);
1558                    plot.debug(p.getName()
1559                            + " could not break hanging entity because hanging-break = false");
1560                }
1561            }
1562        } else if (remover instanceof Projectile p) {
1563            if (p.getShooter() instanceof Player shooter) {
1564                Location location = BukkitUtil.adapt(event.getEntity().getLocation());
1565                PlotArea area = location.getPlotArea();
1566                if (area == null) {
1567                    return;
1568                }
1569                BukkitPlayer player = BukkitUtil.adapt(shooter);
1570                Plot plot = area.getPlot(BukkitUtil.adapt(event.getEntity().getLocation()));
1571                if (plot != null) {
1572                    if (!plot.hasOwner()) {
1573                        if (!player.hasPermission(Permission.PERMISSION_ADMIN_DESTROY_UNOWNED)) {
1574                            player.sendMessage(
1575                                    TranslatableCaption.of("permission.no_permission_event"),
1576                                    TagResolver.resolver(
1577                                            "node",
1578                                            Tag.inserting(Permission.PERMISSION_ADMIN_DESTROY_UNOWNED)
1579                                    )
1580                            );
1581                            event.setCancelled(true);
1582                        }
1583                    } else if (!plot.isAdded(player.getUUID())) {
1584                        if (!plot.getFlag(HangingBreakFlag.class)) {
1585                            if (!player.hasPermission(Permission.PERMISSION_ADMIN_DESTROY_OTHER)) {
1586                                player.sendMessage(
1587                                        TranslatableCaption.of("permission.no_permission_event"),
1588                                        TagResolver.resolver(
1589                                                "node",
1590                                                Tag.inserting(Permission.PERMISSION_ADMIN_DESTROY_OTHER)
1591                                        )
1592                                );
1593                                event.setCancelled(true);
1594                                plot.debug(player.getName()
1595                                        + " could not break hanging entity because hanging-break = false");
1596                            }
1597                        }
1598                    }
1599                }
1600            }
1601        } else {
1602            event.setCancelled(true);
1603        }
1604    }
1605
1606    @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
1607    public void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
1608        if (event.getRightClicked().getType() == EntityType.UNKNOWN) {
1609            return;
1610        }
1611        Location location = BukkitUtil.adapt(event.getRightClicked().getLocation());
1612        PlotArea area = location.getPlotArea();
1613        if (area == null) {
1614            return;
1615        }
1616        Player p = event.getPlayer();
1617        BukkitPlayer pp = BukkitUtil.adapt(p);
1618        Plot plot = area.getPlot(location);
1619        if (plot == null && !area.isRoadFlags()) {
1620            if (!pp.hasPermission(Permission.PERMISSION_ADMIN_INTERACT_ROAD)) {
1621                pp.sendMessage(
1622                        TranslatableCaption.of("permission.no_permission_event"),
1623                        TagResolver.resolver(
1624                                "node",
1625                                Tag.inserting(Permission.PERMISSION_ADMIN_INTERACT_ROAD)
1626                        )
1627                );
1628                event.setCancelled(true);
1629            }
1630        } else if (plot != null && !plot.hasOwner()) {
1631            if (!pp.hasPermission(Permission.PERMISSION_ADMIN_INTERACT_UNOWNED)) {
1632                pp.sendMessage(
1633                        TranslatableCaption.of("permission.no_permission_event"),
1634                        TagResolver.resolver(
1635                                "node",
1636                                Tag.inserting(Permission.PERMISSION_ADMIN_INTERACT_UNOWNED)
1637                        )
1638                );
1639                event.setCancelled(true);
1640            }
1641        } else if ((plot != null && !plot.isAdded(pp.getUUID())) || (plot == null && area
1642                .isRoadFlags())) {
1643            final Entity entity = event.getRightClicked();
1644            final com.sk89q.worldedit.world.entity.EntityType entityType =
1645                    BukkitAdapter.adapt(entity.getType());
1646
1647            FlagContainer flagContainer;
1648            if (plot == null) {
1649                flagContainer = area.getRoadFlagContainer();
1650            } else {
1651                flagContainer = plot.getFlagContainer();
1652            }
1653
1654            if (EntityCategories.HOSTILE.contains(entityType) && flagContainer
1655                    .getFlag(HostileInteractFlag.class).getValue()) {
1656                return;
1657            }
1658
1659            if (EntityCategories.ANIMAL.contains(entityType) && flagContainer
1660                    .getFlag(AnimalInteractFlag.class).getValue()) {
1661                return;
1662            }
1663
1664            // This actually makes use of the interface, so we don't use the
1665            // category
1666            if (entity instanceof Tameable && ((Tameable) entity).isTamed() && flagContainer
1667                    .getFlag(TamedInteractFlag.class).getValue()) {
1668                return;
1669            }
1670
1671            if (EntityCategories.VEHICLE.contains(entityType) && flagContainer
1672                    .getFlag(VehicleUseFlag.class).getValue()) {
1673                return;
1674            }
1675
1676            if (EntityCategories.PLAYER.contains(entityType) && flagContainer
1677                    .getFlag(PlayerInteractFlag.class).getValue()) {
1678                return;
1679            }
1680
1681            if (EntityCategories.VILLAGER.contains(entityType) && flagContainer
1682                    .getFlag(VillagerInteractFlag.class).getValue()) {
1683                return;
1684            }
1685
1686            if ((EntityCategories.HANGING.contains(entityType) || EntityCategories.OTHER
1687                    .contains(entityType)) && flagContainer.getFlag(MiscInteractFlag.class)
1688                    .getValue()) {
1689                return;
1690            }
1691
1692            if (!pp.hasPermission(Permission.PERMISSION_ADMIN_INTERACT_OTHER)) {
1693                pp.sendMessage(
1694                        TranslatableCaption.of("permission.no_permission_event"),
1695                        TagResolver.resolver(
1696                                "node",
1697                                Tag.inserting(Permission.PERMISSION_ADMIN_INTERACT_OTHER)
1698                        )
1699                );
1700                event.setCancelled(true);
1701            }
1702        }
1703    }
1704
1705    @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
1706    public void onVehicleDestroy(VehicleDestroyEvent event) {
1707        Location location = BukkitUtil.adapt(event.getVehicle().getLocation());
1708        PlotArea area = location.getPlotArea();
1709        if (area == null) {
1710            return;
1711        }
1712        Entity attacker = event.getAttacker();
1713        if (attacker instanceof Player p) {
1714            BukkitPlayer pp = BukkitUtil.adapt(p);
1715            Plot plot = area.getPlot(location);
1716            if (plot == null) {
1717                if (!PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, VehicleBreakFlag.class, true) && !pp.hasPermission(
1718                        Permission.PERMISSION_ADMIN_DESTROY_VEHICLE_ROAD
1719                )) {
1720                    pp.sendMessage(
1721                            TranslatableCaption.of("permission.no_permission_event"),
1722                            TagResolver.resolver(
1723                                    "node",
1724                                    Tag.inserting(Permission.PERMISSION_ADMIN_DESTROY_VEHICLE_ROAD)
1725                            )
1726                    );
1727                    event.setCancelled(true);
1728                }
1729            } else {
1730                if (!plot.hasOwner()) {
1731                    if (!pp.hasPermission(Permission.PERMISSION_ADMIN_DESTROY_VEHICLE_UNOWNED)) {
1732                        pp.sendMessage(
1733                                TranslatableCaption.of("permission.no_permission_event"),
1734                                TagResolver.resolver(
1735                                        "node",
1736                                        Tag.inserting(Permission.PERMISSION_ADMIN_DESTROY_VEHICLE_UNOWNED)
1737                                )
1738                        );
1739                        event.setCancelled(true);
1740                        return;
1741                    }
1742                    return;
1743                }
1744                if (!plot.isAdded(pp.getUUID())) {
1745                    if (plot.getFlag(VehicleBreakFlag.class)) {
1746                        return;
1747                    }
1748                    if (!pp.hasPermission(Permission.PERMISSION_ADMIN_DESTROY_VEHICLE_OTHER)) {
1749                        pp.sendMessage(
1750                                TranslatableCaption.of("permission.no_permission_event"),
1751                                TagResolver.resolver(
1752                                        "node",
1753                                        Tag.inserting(Permission.PERMISSION_ADMIN_DESTROY_VEHICLE_OTHER)
1754                                )
1755                        );
1756                        event.setCancelled(true);
1757                        plot.debug(pp.getName()
1758                                + " could not break vehicle because vehicle-break = false");
1759                    }
1760                }
1761            }
1762        }
1763    }
1764
1765    @EventHandler
1766    public void onItemDrop(PlayerDropItemEvent event) {
1767        Player player = event.getPlayer();
1768        BukkitPlayer pp = BukkitUtil.adapt(player);
1769        Location location = pp.getLocation();
1770        PlotArea area = location.getPlotArea();
1771        if (area == null) {
1772            return;
1773        }
1774        Plot plot = location.getOwnedPlot();
1775        if (plot == null) {
1776            if (PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, ItemDropFlag.class, false)) {
1777                event.setCancelled(true);
1778            }
1779            return;
1780        }
1781        UUID uuid = pp.getUUID();
1782        if (!plot.isAdded(uuid)) {
1783            if (!plot.getFlag(ItemDropFlag.class)) {
1784                plot.debug(player.getName() + " could not drop item because of item-drop = false");
1785                event.setCancelled(true);
1786            }
1787        }
1788    }
1789
1790    @EventHandler
1791    public void onItemPickup(EntityPickupItemEvent event) {
1792        LivingEntity ent = event.getEntity();
1793        if (ent instanceof Player player) {
1794            BukkitPlayer pp = BukkitUtil.adapt(player);
1795            Location location = pp.getLocation();
1796            PlotArea area = location.getPlotArea();
1797            if (area == null) {
1798                return;
1799            }
1800            Plot plot = location.getOwnedPlot();
1801            if (plot == null) {
1802                if (PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, DropProtectionFlag.class, true)) {
1803                    event.setCancelled(true);
1804                }
1805                return;
1806            }
1807            UUID uuid = pp.getUUID();
1808            if (!plot.isAdded(uuid) && plot.getFlag(DropProtectionFlag.class)) {
1809                plot.debug(player.getName() + " could not pick up item because of drop-protection = true");
1810                event.setCancelled(true);
1811            }
1812        }
1813    }
1814
1815    @EventHandler
1816    public void onDeath(final PlayerDeathEvent event) {
1817        Location location = BukkitUtil.adapt(event.getEntity().getLocation());
1818        PlotArea area = location.getPlotArea();
1819        if (area == null) {
1820            return;
1821        }
1822        Plot plot = location.getOwnedPlot();
1823        if (plot == null) {
1824            if (PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, KeepInventoryFlag.class, true)) {
1825                event.setCancelled(true);
1826            }
1827            return;
1828        }
1829        if (plot.getFlag(KeepInventoryFlag.class)) {
1830            plot.debug(event.getEntity().getName() + " kept their inventory because of keep-inventory = true");
1831            event.getDrops().clear();
1832            event.setKeepInventory(true);
1833        }
1834    }
1835
1836    @SuppressWarnings("deprecation") // #getLocate is needed for Spigot compatibility
1837    @EventHandler
1838    public void onLocaleChange(final PlayerLocaleChangeEvent event) {
1839        // The event is fired before the player is deemed online upon login
1840        if (!event.getPlayer().isOnline()) {
1841            return;
1842        }
1843        BukkitPlayer player = BukkitUtil.adapt(event.getPlayer());
1844        // we're stripping the country code as we don't want to differ between countries
1845        player.setLocale(Locale.forLanguageTag(event.getLocale().substring(0, 2)));
1846    }
1847
1848    @EventHandler
1849    public void onPortalEnter(PlayerPortalEvent event) {
1850        Location location = BukkitUtil.adapt(event.getPlayer().getLocation());
1851        PlotArea area = location.getPlotArea();
1852        if (area == null) {
1853            return;
1854        }
1855        Plot plot = location.getOwnedPlot();
1856        if (plot == null) {
1857            if (PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, DenyPortalTravelFlag.class, true)) {
1858                event.setCancelled(true);
1859            }
1860            return;
1861        }
1862        if (plot.getFlag(DenyPortalTravelFlag.class)) {
1863            plot.debug(event.getPlayer().getName() + " did not travel thru a portal because of deny-portal-travel = true");
1864            event.setCancelled(true);
1865        }
1866    }
1867
1868    @EventHandler
1869    public void onPortalCreation(PortalCreateEvent event) {
1870        String world = event.getWorld().getName();
1871        if (PlotSquared.get().getPlotAreaManager().getPlotAreasSet(world).size() == 0) {
1872            return;
1873        }
1874        BukkitPlayer pp = (event.getEntity() instanceof Player player) ? BukkitUtil.adapt(player) : null;
1875        int minX = Integer.MAX_VALUE;
1876        int maxX = Integer.MIN_VALUE;
1877        int minZ = Integer.MAX_VALUE;
1878        int maxZ = Integer.MIN_VALUE;
1879        for (BlockState state : event.getBlocks()) {
1880            minX = Math.min(state.getX(), minX);
1881            maxX = Math.max(state.getX(), maxX);
1882            minZ = Math.min(state.getZ(), minZ);
1883            maxZ = Math.max(state.getZ(), maxZ);
1884        }
1885        int y = event.getBlocks().get(0).getY(); // Don't need to worry about this too much
1886        for (Location location : List.of( // We don't care about duplicate locations
1887                Location.at(world, minX, y, minZ),
1888                Location.at(world, minX, y, maxZ),
1889                Location.at(world, maxX, y, minZ),
1890                Location.at(world, maxX, y, maxZ)
1891        )) {
1892            PlotArea area = location.getPlotArea();
1893            if (area == null) {
1894                continue;
1895            }
1896            if (area.notifyIfOutsideBuildArea(pp, location.getY())) {
1897                event.setCancelled(true);
1898                return;
1899            }
1900            Plot plot = location.getOwnedPlot();
1901            if (plot == null) {
1902                if (PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, DenyPortalsFlag.class, true)) {
1903                    event.setCancelled(true);
1904                    return;
1905                }
1906                continue;
1907            }
1908            if (plot.getFlag(DenyPortalsFlag.class)) {
1909                StringBuilder builder = new StringBuilder();
1910                if (event.getEntity() != null) {
1911                    builder.append(event.getEntity().getName()).append(" did not create a portal");
1912                } else {
1913                    builder.append("Portal creation cancelled");
1914                }
1915                plot.debug(builder.append(" because of deny-portals = true").toString());
1916                event.setCancelled(true);
1917                return;
1918            }
1919        }
1920    }
1921
1922    @EventHandler
1923    public void onPlayerTakeLecternBook(PlayerTakeLecternBookEvent event) {
1924        Location location = BukkitUtil.adapt(event.getPlayer().getLocation());
1925        PlotArea area = location.getPlotArea();
1926        if (area == null) {
1927            return;
1928        }
1929        Plot plot = location.getOwnedPlot();
1930        if (plot == null) {
1931            if (PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, LecternReadBookFlag.class, true)) {
1932                event.setCancelled(true);
1933            }
1934            return;
1935        }
1936        if (plot.getFlag(LecternReadBookFlag.class)) {
1937            plot.debug(event.getPlayer().getName() + " could not take the book because of lectern-read-book = true");
1938            event.setCancelled(true);
1939        }
1940    }
1941
1942}