diff --git a/config/checkstyle/import-control.xml b/config/checkstyle/import-control.xml index abb251d1c..f51b28042 100644 --- a/config/checkstyle/import-control.xml +++ b/config/checkstyle/import-control.xml @@ -31,6 +31,7 @@ + diff --git a/worldguard-bukkit/build.gradle.kts b/worldguard-bukkit/build.gradle.kts index efa607c52..ad1d211eb 100644 --- a/worldguard-bukkit/build.gradle.kts +++ b/worldguard-bukkit/build.gradle.kts @@ -20,6 +20,13 @@ dependencies { exclude("org.slf4j", "slf4j-api") exclude("junit", "junit") } + // Vendored locally: just the 3 io.canvasmc.canvas.event.*TeleportAsyncEvent* class + // files (extracted from canvas-api), not the whole canvas-api jar - that jar bundles + // its own full copy of org.bukkit.* compiled for a newer JDK, which conflicts with + // paperApi on the compile classpath. CanvasMC doesn't publish canvas-api for this old + // a version line anyway. Only used at compile time - see WorldGuardCanvasListener + // and WorldGuardPlugin#isCanvas(). + "compileOnly"(files("libs/canvas-teleport-events.jar")) "implementation"(libs.paperLib) "implementation"(libs.bstats.bukkit) diff --git a/worldguard-bukkit/libs/canvas-teleport-events.jar b/worldguard-bukkit/libs/canvas-teleport-events.jar new file mode 100644 index 000000000..575f094ad Binary files /dev/null and b/worldguard-bukkit/libs/canvas-teleport-events.jar differ diff --git a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/WorldGuardPlugin.java b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/WorldGuardPlugin.java index 9229d1db5..52656aa89 100644 --- a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/WorldGuardPlugin.java +++ b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/WorldGuardPlugin.java @@ -53,6 +53,7 @@ import com.sk89q.worldguard.bukkit.listener.WorldGuardBlockListener; import com.sk89q.worldguard.bukkit.listener.WorldGuardCommandBookListener; import com.sk89q.worldguard.bukkit.listener.WorldGuardEntityListener; +import com.sk89q.worldguard.bukkit.listener.WorldGuardCanvasListener; import com.sk89q.worldguard.bukkit.listener.WorldGuardHangingListener; import com.sk89q.worldguard.bukkit.listener.WorldGuardPlayerListener; import com.sk89q.worldguard.bukkit.listener.WorldGuardServerListener; @@ -190,6 +191,9 @@ public void accept(Object ignored) { (new WorldGuardVehicleListener(this)).registerEvents(); (new WorldGuardServerListener(this)).registerEvents(); (new WorldGuardHangingListener(this)).registerEvents(); + if (this.isCanvas()) { + (new WorldGuardCanvasListener(this)).registerEvents(); + } // Modules (playerMoveListener = new PlayerMoveListener(this)).registerEvents(); @@ -575,4 +579,26 @@ public boolean isFolia() { return folia.getValue(); } + private final LazyReference canvas = LazyReference.from(() -> { + try { + Class.forName("io.canvasmc.canvas.event.EntityTeleportAsyncEvent"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + }); + + /** + * Whether the server exposes CanvasMC's own teleport event API + * ({@code io.canvasmc.canvas.event.EntityTeleportAsyncEvent}). Canvas (a Folia + * fork) documents that the vanilla {@link org.bukkit.event.player.PlayerTeleportEvent} + * does not reliably fire for entity-driven teleports under region threading, and + * added this replacement instead of fixing the old API. + * + * @see Canvas events docs + */ + public boolean isCanvas() { + return canvas.getValue(); + } + } diff --git a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/EventAbstractionListener.java b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/EventAbstractionListener.java index d91114a52..c3a358e89 100644 --- a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/EventAbstractionListener.java +++ b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/EventAbstractionListener.java @@ -343,7 +343,15 @@ private void setDelegateEventMaterialOptions(DelegateEvent event, Material fromT @EventHandler(ignoreCancelled = true) public void onEntityChangeBlock(EntityChangeBlockEvent event) { - Block block = event.getBlock(); + Block block; + try { + block = event.getBlock(); + } catch (NullPointerException e) { + // Some platforms can fire this event referencing a block whose location no + // longer resolves to a loaded world. Nothing useful can be done, so bail out. + // See EngineHub/WorldGuard#2238 + return; + } Entity entity = event.getEntity(); Material toType = event.getTo(); Material fromType = block.getType(); diff --git a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardCanvasListener.java b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardCanvasListener.java new file mode 100644 index 000000000..69adc1bd5 --- /dev/null +++ b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardCanvasListener.java @@ -0,0 +1,116 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.config.ConfigurationManager; +import com.sk89q.worldguard.config.WorldConfiguration; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.StateFlag; +import com.sk89q.worldguard.protection.regions.RegionQuery; +import io.canvasmc.canvas.event.EntityTeleportAsyncEvent; +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; +import org.bukkit.inventory.ItemStack; + +/** + * Re-implements the ENDERPEARL/CHORUS_TELEPORT flag checks using Canvas's own + * {@link EntityTeleportAsyncEvent} instead of the vanilla + * {@link org.bukkit.event.player.PlayerTeleportEvent}. + * + *

Canvas (a Folia fork) documents that the vanilla event "doesn't function" for + * entity-driven teleports under region threading, and added this event as a + * replacement rather than fixing the old API. This listener is only registered when + * {@link WorldGuardPlugin#isCanvas()} is true, so it has no effect on other platforms.

+ * + * @see Canvas events docs + */ +public class WorldGuardCanvasListener extends AbstractListener { + + public WorldGuardCanvasListener(WorldGuardPlugin plugin) { + super(plugin); + } + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onEntityTeleportAsync(EntityTeleportAsyncEvent event) { + if (!(event.getEntity() instanceof Player player)) { + return; + } + if (com.sk89q.worldguard.bukkit.util.Entities.isNPC(player)) { + return; + } + + TeleportCause cause = event.getCause(); + StateFlag flag; + if (cause == TeleportCause.ENDER_PEARL) { + flag = Flags.ENDERPEARL; + } else if (cause == TeleportCause.CHORUS_FRUIT) { + flag = Flags.CHORUS_TELEPORT; + } else { + return; + } + + LocalPlayer localPlayer = getPlugin().wrapPlayer(player); + ConfigurationManager cfg = getConfig(); + WorldConfiguration wcfg = getWorldConfig(player.getWorld()); + + if (!wcfg.useRegions || !cfg.usePlayerTeleports) { + return; + } + if (WorldGuard.getInstance().getPlatform().getSessionManager().hasBypass(localPlayer, localPlayer.getWorld())) { + return; + } + + RegionQuery query = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery(); + ApplicableRegionSet setFrom = query.getApplicableRegions(BukkitAdapter.adapt(event.getFrom())); + ApplicableRegionSet setTo = query.getApplicableRegions(BukkitAdapter.adapt(event.getTo())); + + boolean cancel = false; + String message = null; + if (!setFrom.testState(localPlayer, flag)) { + cancel = true; + message = setFrom.queryValue(localPlayer, Flags.EXIT_DENY_MESSAGE); + } else if (!setTo.testState(localPlayer, flag)) { + cancel = true; + message = setTo.queryValue(localPlayer, Flags.ENTRY_DENY_MESSAGE); + } + + if (cancel) { + if (message != null && !message.isEmpty()) { + player.sendMessage(message); + } + event.setCancelled(true); + // The pearl/fruit is consumed before this event fires, so give it back. + if (player.getGameMode() != GameMode.CREATIVE) { + Material refund = cause == TeleportCause.ENDER_PEARL ? Material.ENDER_PEARL : Material.CHORUS_FRUIT; + player.getInventory().addItem(new ItemStack(refund, 1)); + } + } + } + +} diff --git a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardEntityListener.java b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardEntityListener.java index 080a68db5..c892843c1 100644 --- a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardEntityListener.java +++ b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardEntityListener.java @@ -812,9 +812,19 @@ public void onFoodChange(FoodLevelChangeEvent event) { public void onEntityChangeBlock(EntityChangeBlockEvent event) { Entity ent = event.getEntity(); + Block changedBlock; + try { + changedBlock = event.getBlock(); + } catch (NullPointerException e) { + // Some platforms can fire this event referencing a block whose location no + // longer resolves to a loaded world. Nothing useful can be done, so bail out. + // See EngineHub/WorldGuard#2238 + return; + } + WorldConfiguration wcfg = getWorldConfig(ent.getWorld()); if (ent instanceof FallingBlock) { - Material id = event.getBlock().getType(); + Material id = changedBlock.getType(); if (id == Material.GRAVEL && wcfg.noPhysicsGravel) { event.setCancelled(true); @@ -836,7 +846,7 @@ public void onEntityChangeBlock(EntityChangeBlockEvent event) { return; } if (wcfg.useRegions) { - Location location = event.getBlock().getLocation(); + Location location = changedBlock.getLocation(); if (!StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().queryState(BukkitAdapter.adapt(location), (RegionAssociable) null, Flags.WITHER_DAMAGE))) { event.setCancelled(true); return; diff --git a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardHangingListener.java b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardHangingListener.java index 9d93a5191..517dccf90 100644 --- a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardHangingListener.java +++ b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardHangingListener.java @@ -92,13 +92,20 @@ public void onHangingBreak(HangingBreakEvent event) { event.setCancelled(true); } } - } else { - // Explosions from mobs are not covered by HangingBreakByEntity - if (hanging instanceof Painting && wcfg.blockEntityPaintingDestroy - && event.getCause() == RemoveCause.EXPLOSION) { + } else if (event.getCause() == RemoveCause.EXPLOSION || event.getCause() == RemoveCause.PHYSICS) { + // Explosions from mobs, and physics-caused breaks (e.g. a boat colliding + // with the hanging entity), are not covered by HangingBreakByEntityEvent, + // so there's no attacker entity available to check here. + // See EngineHub/WorldGuard#1434 for the PHYSICS case (boats breaking item frames). + if (hanging instanceof Painting + && (wcfg.blockEntityPaintingDestroy + || (wcfg.useRegions + && !StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().queryState(BukkitAdapter.adapt(hanging.getLocation()), (RegionAssociable) null, Flags.ENTITY_PAINTING_DESTROY))))) { event.setCancelled(true); - } else if (hanging instanceof ItemFrame && wcfg.blockEntityItemFrameDestroy - && event.getCause() == RemoveCause.EXPLOSION) { + } else if (hanging instanceof ItemFrame + && (wcfg.blockEntityItemFrameDestroy + || (wcfg.useRegions + && !StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().queryState(BukkitAdapter.adapt(hanging.getLocation()), (RegionAssociable) null, Flags.ENTITY_ITEM_FRAME_DESTROY))))) { event.setCancelled(true); } } diff --git a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardPlayerListener.java b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardPlayerListener.java index 1656ab323..da08126d7 100644 --- a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardPlayerListener.java +++ b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardPlayerListener.java @@ -382,6 +382,10 @@ public void onPlayerTeleport(PlayerTeleportEvent event) { player.sendMessage(message); } event.setCancelled(true); + // The pearl is consumed on throw, before this event fires, so give it back. + if (player.getGameMode() != org.bukkit.GameMode.CREATIVE) { + player.getInventory().addItem(new ItemStack(Material.ENDER_PEARL, 1)); + } return; } } @@ -401,6 +405,10 @@ public void onPlayerTeleport(PlayerTeleportEvent event) { player.sendMessage(message); } event.setCancelled(true); + // The fruit is consumed on eating, before this event fires, so give it back. + if (player.getGameMode() != org.bukkit.GameMode.CREATIVE) { + player.getInventory().addItem(new ItemStack(Material.CHORUS_FRUIT, 1)); + } return; } } diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/logger/FileHandler.java b/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/logger/FileHandler.java index df146c996..d7c4ff26e 100644 --- a/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/logger/FileHandler.java +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/logger/FileHandler.java @@ -121,7 +121,8 @@ public FileHandler(String pathPattern, int cacheSize, String worldName, Logger l } else if (group.matches("%Y")) { rep = String.valueOf(calendar.get(Calendar.YEAR)); } else if (group.matches("%m")) { - rep = String.format("%02d", calendar.get(Calendar.MONTH)); + // Calendar.MONTH is 0-indexed (January = 0), but the %m log path token should be 1-indexed. + rep = String.format("%02d", calendar.get(Calendar.MONTH) + 1); } else if (group.matches("%d")) { rep = String.format("%02d", calendar.get(Calendar.DAY_OF_MONTH)); } else if (group.matches("%W")) { diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommands.java b/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommands.java index 9299ad02b..fec4b883c 100644 --- a/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommands.java +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommands.java @@ -597,7 +597,9 @@ public void flag(CommandContext args, Actor sender) throws CommandException { if (value != null) { // Set the flag if [value] was given even if [-g group] was given as well try { - value = setFlag(existing, foundFlag, sender, value).toString(); + Object parsedValue = setFlag(existing, foundFlag, sender, value); + // Some flags (e.g. StateFlag) parse "none" to a null value to clear the flag. + value = parsedValue == null ? "none" : parsedValue.toString(); } catch (InvalidFlagFormat e) { throw new CommandException(e.getMessage()); }