From fbb0011997e37ab2daae0a6c0b67f1ca512d21d9 Mon Sep 17 00:00:00 2001 From: voidsip Date: Tue, 3 Nov 2026 05:49:01 -0600 Subject: [PATCH] Use chest-access for inventory minecart interactions --- config/checkstyle/import-control.xml | 1 + gradle/libs.versions.toml | 1 + worldguard-bukkit/build.gradle.kts | 3 + .../listener/RegionProtectionListener.java | 8 +- .../RegionProtectionListenerTest.java | 136 ++++++++++++++++++ 5 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 worldguard-bukkit/src/test/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListenerTest.java diff --git a/config/checkstyle/import-control.xml b/config/checkstyle/import-control.xml index abb251d1c..6ac7340bf 100644 --- a/config/checkstyle/import-control.xml +++ b/config/checkstyle/import-control.xml @@ -24,6 +24,7 @@ + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f6347abb5..fbfe23df9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -40,6 +40,7 @@ flyway-core = "org.flywaydb:flyway-core:3.0" commandbook = "com.sk89q:commandbook:2.3" hamcrest-library = "org.hamcrest:hamcrest-library:2.2" +mockito-core = "org.mockito:mockito-core:5.22.0" # Mojang-provided libraries, CHECK AGAINST MINECRAFT for versions guava = "com.google.guava:guava:33.6.0-jre" diff --git a/worldguard-bukkit/build.gradle.kts b/worldguard-bukkit/build.gradle.kts index 73629773f..5dd42eb4e 100644 --- a/worldguard-bukkit/build.gradle.kts +++ b/worldguard-bukkit/build.gradle.kts @@ -23,6 +23,9 @@ dependencies { "implementation"(libs.paperLib) "implementation"(libs.bstats.bukkit) + + "testImplementation"(libs.paperApi) + "testImplementation"(libs.mockito.core) } tasks.named("processResources") { diff --git a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListener.java b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListener.java index 5351d08f0..75d4dfea5 100644 --- a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListener.java +++ b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListener.java @@ -56,6 +56,7 @@ import org.bukkit.entity.ExperienceOrb; import org.bukkit.entity.Item; import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Minecart; import org.bukkit.entity.Player; import org.bukkit.entity.SulfurCube; import org.bukkit.entity.Tameable; @@ -66,8 +67,10 @@ import org.bukkit.event.entity.PlayerLeashEntityEvent; import org.bukkit.event.inventory.InventoryMoveItemEvent; import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; import org.bukkit.event.player.PlayerTakeLecternBookEvent; import org.bukkit.event.vehicle.VehicleExitEvent; +import org.bukkit.inventory.InventoryHolder; import java.util.ArrayList; import java.util.Arrays; @@ -423,7 +426,10 @@ public void onUseEntity(UseEntityEvent event) { && ((ItemFrame) entity).getItem().getType() != Material.AIR) { canUse = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.ITEM_FRAME_ROTATE)); what = "change that"; - } else if (event.getOriginalEvent() instanceof InventoryOpenEvent || event.getOriginalEvent() instanceof InventoryMoveItemEvent) { + } else if (event.getOriginalEvent() instanceof InventoryOpenEvent + || event.getOriginalEvent() instanceof InventoryMoveItemEvent + || (event.getOriginalEvent() instanceof PlayerInteractEntityEvent + && entity instanceof Minecart && entity instanceof InventoryHolder)) { canUse = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.CHEST_ACCESS)); what = "open that"; } else { diff --git a/worldguard-bukkit/src/test/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListenerTest.java b/worldguard-bukkit/src/test/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListenerTest.java new file mode 100644 index 000000000..0c6d431e0 --- /dev/null +++ b/worldguard-bukkit/src/test/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListenerTest.java @@ -0,0 +1,136 @@ +/* + * 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.worldedit.math.BlockVector3; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.bukkit.cause.Cause; +import com.sk89q.worldguard.bukkit.event.entity.UseEntityEvent; +import com.sk89q.worldguard.internal.platform.WorldGuardPlatform; +import com.sk89q.worldguard.protection.RegionResultSet; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.StateFlag.State; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.RegionContainer; +import com.sk89q.worldguard.protection.regions.RegionQuery; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Minecart; +import org.bukkit.entity.minecart.HopperMinecart; +import org.bukkit.entity.minecart.StorageMinecart; +import org.bukkit.event.Event; +import org.bukkit.event.inventory.InventoryMoveItemEvent; +import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +class RegionProtectionListenerTest { + + @Test + void chestAccessAllowsInitialInventoryMinecartClick() { + assertFalse(checkUse(StorageMinecart.class, EntityType.CHEST_MINECART, + PlayerInteractEntityEvent.class, State.ALLOW)); + assertFalse(checkUse(HopperMinecart.class, EntityType.HOPPER_MINECART, + PlayerInteractEntityEvent.class, State.ALLOW)); + } + + @Test + void chestAccessStillDeniesInventoryMinecartClick() { + assertTrue(checkUse(StorageMinecart.class, EntityType.CHEST_MINECART, + PlayerInteractEntityEvent.class, State.DENY)); + } + + @Test + void inventoryOpenAndTransferStillUseChestAccess() { + assertFalse(checkUse(StorageMinecart.class, EntityType.CHEST_MINECART, + InventoryOpenEvent.class, State.ALLOW)); + assertTrue(checkUse(StorageMinecart.class, EntityType.CHEST_MINECART, + InventoryMoveItemEvent.class, State.DENY)); + } + + @Test + void chestAccessDoesNotAllowOtherEntityChanges() { + assertTrue(checkUse(ArmorStand.class, EntityType.ARMOR_STAND, + PlayerInteractEntityEvent.class, State.ALLOW)); + } + + @Test + void ordinaryMinecartStillDefersToMountProtection() { + assertFalse(checkUse(Minecart.class, EntityType.MINECART, + PlayerInteractEntityEvent.class, State.DENY)); + } + + private boolean checkUse( + Class entityClass, EntityType type, + Class eventClass, State chestAccess) { + ProtectedCuboidRegion region = new ProtectedCuboidRegion("test", + BlockVector3.at(-1, 0, -1), BlockVector3.at(1, 100, 1)); + region.setFlag(Flags.CHEST_ACCESS, chestAccess); + RegionQuery query = mock(RegionQuery.class, CALLS_REAL_METHODS); + doReturn(new RegionResultSet(new ArrayList<>(List.of(region)), null)).when(query) + .getApplicableRegions(any(com.sk89q.worldedit.util.Location.class)); + + WorldGuard worldGuard = mock(WorldGuard.class); + WorldGuardPlatform platform = mock(WorldGuardPlatform.class); + RegionContainer container = mock(RegionContainer.class); + when(worldGuard.getPlatform()).thenReturn(platform); + when(platform.getRegionContainer()).thenReturn(container); + when(container.createQuery()).thenReturn(query); + + World world = mock(World.class); + T entity = mock(entityClass); + when(entity.getType()).thenReturn(type); + when(entity.getLocation()).thenReturn(new Location(world, 0, 64, 0)); + UseEntityEvent event = new UseEntityEvent(mock(eventClass), Cause.unknown(), entity); + event.setSilent(true); + + var worldEditWorld = mock(com.sk89q.worldedit.world.World.class); + var worldEditLocation = new com.sk89q.worldedit.util.Location( + worldEditWorld, 0, 64, 0); + try (MockedStatic guard = mockStatic(WorldGuard.class); + MockedStatic listener = mockStatic(AbstractListener.class); + MockedStatic adapter = mockStatic(BukkitAdapter.class)) { + guard.when(WorldGuard::getInstance).thenReturn(worldGuard); + listener.when(() -> AbstractListener.isRegionSupportEnabled(world)).thenReturn(true); + adapter.when(() -> BukkitAdapter.adapt(any(Location.class))) + .thenReturn(worldEditLocation); + new RegionProtectionListener(mock(WorldGuardPlugin.class)).onUseEntity(event); + } + return event.isCancelled(); + } +}