This commit is contained in:
@@ -21,6 +21,17 @@ dependencies {
|
|||||||
compileOnly("com.github.GriefPrevention:GriefPrevention:16.18.6") {
|
compileOnly("com.github.GriefPrevention:GriefPrevention:16.18.6") {
|
||||||
isTransitive = false
|
isTransitive = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testImplementation("io.papermc.paper:paper-api:26.2.build.+")
|
||||||
|
testImplementation("com.github.Zrips:Jobs:v5.2.6.2") {
|
||||||
|
isTransitive = false
|
||||||
|
}
|
||||||
|
testImplementation("com.github.GriefPrevention:GriefPrevention:16.18.6") {
|
||||||
|
isTransitive = false
|
||||||
|
}
|
||||||
|
testImplementation("org.junit.jupiter:junit-jupiter:5.11.3")
|
||||||
|
testImplementation("org.mockito:mockito-core:5.23.0")
|
||||||
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.11.3")
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
@@ -32,6 +43,10 @@ tasks.withType<JavaCompile>().configureEach {
|
|||||||
options.compilerArgs.addAll(listOf("-Xlint:deprecation", "-Xlint:unchecked"))
|
options.compilerArgs.addAll(listOf("-Xlint:deprecation", "-Xlint:unchecked"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
tasks.processResources {
|
tasks.processResources {
|
||||||
inputs.property("pluginVersion", pluginVersion)
|
inputs.property("pluginVersion", pluginVersion)
|
||||||
filesMatching("plugin.yml") {
|
filesMatching("plugin.yml") {
|
||||||
|
|||||||
@@ -8,5 +8,7 @@ import org.bukkit.World;
|
|||||||
interface EmergencyLocationProvider {
|
interface EmergencyLocationProvider {
|
||||||
EmergencySite randomClaimSite(Random random, Predicate<World> worldAllowed, int maxRadius, int minClaimArea);
|
EmergencySite randomClaimSite(Random random, Predicate<World> worldAllowed, int maxRadius, int minClaimArea);
|
||||||
|
|
||||||
|
EmergencySite associateClaim(EmergencySite site);
|
||||||
|
|
||||||
String nearbyClaims(Location location, int radius, int maxResults);
|
String nearbyClaims(Location location, int radius, int maxResults);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -280,6 +280,10 @@ final class EmergencyManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (locationProvider != null) {
|
||||||
|
site = locationProvider.associateClaim(site);
|
||||||
|
}
|
||||||
|
|
||||||
activeSite = site;
|
activeSite = site;
|
||||||
activeFires.addAll(spawned);
|
activeFires.addAll(spawned);
|
||||||
if (construction != null) {
|
if (construction != null) {
|
||||||
|
|||||||
@@ -4,7 +4,15 @@ import org.bukkit.Bukkit;
|
|||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
|
|
||||||
record EmergencySite(String name, String worldName, int x, int y, int z, int radius) {
|
record EmergencySite(String name, String worldName, int x, int y, int z, int radius, Long griefPreventionClaimId) {
|
||||||
|
EmergencySite(String name, String worldName, int x, int y, int z, int radius) {
|
||||||
|
this(name, worldName, x, y, z, radius, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
EmergencySite withGriefPreventionClaimId(Long claimId) {
|
||||||
|
return new EmergencySite(name, worldName, x, y, z, radius, claimId);
|
||||||
|
}
|
||||||
|
|
||||||
Location center() {
|
Location center() {
|
||||||
World world = Bukkit.getWorld(worldName);
|
World world = Bukkit.getWorld(worldName);
|
||||||
return world == null ? null : new Location(world, x + 0.5, y, z + 0.5);
|
return world == null ? null : new Location(world, x + 0.5, y, z + 0.5);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import org.bukkit.event.Listener;
|
|||||||
import org.bukkit.event.block.BlockBreakEvent;
|
import org.bukkit.event.block.BlockBreakEvent;
|
||||||
import org.bukkit.event.block.BlockPlaceEvent;
|
import org.bukkit.event.block.BlockPlaceEvent;
|
||||||
import org.bukkit.event.player.PlayerBucketEmptyEvent;
|
import org.bukkit.event.player.PlayerBucketEmptyEvent;
|
||||||
|
import org.bukkit.event.player.PlayerInteractEvent;
|
||||||
|
|
||||||
final class GriefPreventionBridge implements Listener {
|
final class GriefPreventionBridge implements Listener {
|
||||||
private final EmergencyManager emergencies;
|
private final EmergencyManager emergencies;
|
||||||
@@ -25,7 +26,7 @@ final class GriefPreventionBridge implements Listener {
|
|||||||
this.construction = construction;
|
this.construction = construction;
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = false)
|
||||||
public void onClaimPermissionCheck(ClaimPermissionCheckEvent event) {
|
public void onClaimPermissionCheck(ClaimPermissionCheckEvent event) {
|
||||||
if (event.getRequiredPermission() != ClaimPermission.Build || event.getDenialReason() == null) {
|
if (event.getRequiredPermission() != ClaimPermission.Build || event.getDenialReason() == null) {
|
||||||
return;
|
return;
|
||||||
@@ -36,6 +37,11 @@ final class GriefPreventionBridge implements Listener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EmergencySite activeSite = emergencies.activeSite();
|
||||||
|
if (!isIncidentClaim(activeSite, event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Event trigger = event.getTriggeringEvent();
|
Event trigger = event.getTriggeringEvent();
|
||||||
if (trigger instanceof BlockPlaceEvent placeEvent) {
|
if (trigger instanceof BlockPlaceEvent placeEvent) {
|
||||||
allowIfInside(event, placeEvent.getBlock().getLocation());
|
allowIfInside(event, placeEvent.getBlock().getLocation());
|
||||||
@@ -47,6 +53,14 @@ final class GriefPreventionBridge implements Listener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (trigger instanceof PlayerInteractEvent interactEvent) {
|
||||||
|
Block block = interactEvent.getClickedBlock();
|
||||||
|
if (block != null) {
|
||||||
|
allowIfInside(event, block.getLocation());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (trigger instanceof BlockBreakEvent breakEvent) {
|
if (trigger instanceof BlockBreakEvent breakEvent) {
|
||||||
Block block = breakEvent.getBlock();
|
Block block = breakEvent.getBlock();
|
||||||
if (permissions.isInsideActiveSite(block.getLocation())
|
if (permissions.isInsideActiveSite(block.getLocation())
|
||||||
@@ -58,6 +72,14 @@ final class GriefPreventionBridge implements Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isIncidentClaim(EmergencySite activeSite, ClaimPermissionCheckEvent event) {
|
||||||
|
if (activeSite == null || activeSite.griefPreventionClaimId() == null || event.getClaim() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Long checkedClaimId = event.getClaim().getID();
|
||||||
|
return checkedClaimId != null && checkedClaimId.equals(activeSite.griefPreventionClaimId());
|
||||||
|
}
|
||||||
|
|
||||||
private void allowIfInside(ClaimPermissionCheckEvent event, Location location) {
|
private void allowIfInside(ClaimPermissionCheckEvent event, Location location) {
|
||||||
if (permissions.isInsideActiveSite(location)) {
|
if (permissions.isInsideActiveSite(location)) {
|
||||||
event.setDenialReason(null);
|
event.setDenialReason(null);
|
||||||
|
|||||||
@@ -60,6 +60,21 @@ final class GriefPreventionLocationProvider implements EmergencyLocationProvider
|
|||||||
return new EmergencySite(owner + "'s home", world.getName(), centerX, y, centerZ, radius);
|
return new EmergencySite(owner + "'s home", world.getName(), centerX, y, centerZ, radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EmergencySite associateClaim(EmergencySite site) {
|
||||||
|
if (site == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location center = site.center();
|
||||||
|
if (center == null || center.getWorld() == null) {
|
||||||
|
return site.withGriefPreventionClaimId(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Claim claim = GriefPrevention.instance.dataStore.getClaimAt(center, false, null);
|
||||||
|
return site.withGriefPreventionClaimId(claim == null ? null : claim.getID());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String nearbyClaims(Location location, int radius, int maxResults) {
|
public String nearbyClaims(Location location, int radius, int maxResults) {
|
||||||
if (location == null || location.getWorld() == null) {
|
if (location == null || location.getWorld() == null) {
|
||||||
|
|||||||
@@ -0,0 +1,217 @@
|
|||||||
|
package net.therosegarden.firefighter;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import me.ryanhamshire.GriefPrevention.Claim;
|
||||||
|
import me.ryanhamshire.GriefPrevention.ClaimPermission;
|
||||||
|
import me.ryanhamshire.GriefPrevention.events.ClaimPermissionCheckEvent;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.block.BlockBreakEvent;
|
||||||
|
import org.bukkit.event.block.BlockPlaceEvent;
|
||||||
|
import org.bukkit.event.player.PlayerInteractEvent;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class GriefPreventionBridgeTest {
|
||||||
|
private static final Long INCIDENT_CLAIM_ID = 42L;
|
||||||
|
private static final Supplier<String> DENIED = () -> "denied";
|
||||||
|
|
||||||
|
private EmergencyManager emergencies;
|
||||||
|
private EmergencyPermissionManager permissions;
|
||||||
|
private EmergencyConstructionManager construction;
|
||||||
|
private GriefPreventionBridge bridge;
|
||||||
|
private Player player;
|
||||||
|
private EmergencySite incidentSite;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
emergencies = mock(EmergencyManager.class);
|
||||||
|
permissions = mock(EmergencyPermissionManager.class);
|
||||||
|
construction = mock(EmergencyConstructionManager.class);
|
||||||
|
bridge = new GriefPreventionBridge(emergencies, permissions, construction);
|
||||||
|
player = mock(Player.class);
|
||||||
|
when(player.getUniqueId()).thenReturn(UUID.randomUUID());
|
||||||
|
incidentSite = new EmergencySite("Incident", "world", 100, 64, 100, 18, INCIDENT_CLAIM_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void alreadyDeniedPlayerInteractBuildIsProcessedAndClearedForEligibleIncident() {
|
||||||
|
Block block = mock(Block.class);
|
||||||
|
Location location = mock(Location.class);
|
||||||
|
when(block.getLocation()).thenReturn(location);
|
||||||
|
|
||||||
|
PlayerInteractEvent trigger = mock(PlayerInteractEvent.class);
|
||||||
|
when(trigger.getClickedBlock()).thenReturn(block);
|
||||||
|
|
||||||
|
allowEligibleIncident(location);
|
||||||
|
ClaimPermissionCheckEvent event = deniedEvent(ClaimPermission.Build, trigger, INCIDENT_CLAIM_ID);
|
||||||
|
|
||||||
|
assertTrue(event.isCancelled(), "GriefPrevention denial should make the permission event cancelled before RoseFirefighter handles it");
|
||||||
|
bridge.onClaimPermissionCheck(event);
|
||||||
|
|
||||||
|
assertFalse(event.isCancelled());
|
||||||
|
assertNull(event.getDenialReason());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void correctIncidentClaimAllowsPlaceAndBreakWithoutUncancellingTriggerEvents() {
|
||||||
|
Block placeBlock = mock(Block.class);
|
||||||
|
Location placeLocation = mock(Location.class);
|
||||||
|
when(placeBlock.getLocation()).thenReturn(placeLocation);
|
||||||
|
BlockPlaceEvent placeTrigger = mock(BlockPlaceEvent.class);
|
||||||
|
when(placeTrigger.getBlock()).thenReturn(placeBlock);
|
||||||
|
|
||||||
|
Block breakBlock = mock(Block.class);
|
||||||
|
Location breakLocation = mock(Location.class);
|
||||||
|
when(breakBlock.getLocation()).thenReturn(breakLocation);
|
||||||
|
BlockBreakEvent breakTrigger = mock(BlockBreakEvent.class);
|
||||||
|
when(breakTrigger.getBlock()).thenReturn(breakBlock);
|
||||||
|
|
||||||
|
when(permissions.hasBuildAccess(player)).thenReturn(true);
|
||||||
|
when(emergencies.activeSite()).thenReturn(incidentSite);
|
||||||
|
when(permissions.isInsideActiveSite(placeLocation)).thenReturn(true);
|
||||||
|
when(permissions.isInsideActiveSite(breakLocation)).thenReturn(true);
|
||||||
|
when(construction.isForcedEntryEnabled()).thenReturn(true);
|
||||||
|
|
||||||
|
ClaimPermissionCheckEvent placeEvent = deniedEvent(ClaimPermission.Build, placeTrigger, INCIDENT_CLAIM_ID);
|
||||||
|
ClaimPermissionCheckEvent breakEvent = deniedEvent(ClaimPermission.Build, breakTrigger, INCIDENT_CLAIM_ID);
|
||||||
|
|
||||||
|
bridge.onClaimPermissionCheck(placeEvent);
|
||||||
|
bridge.onClaimPermissionCheck(breakEvent);
|
||||||
|
|
||||||
|
assertNull(placeEvent.getDenialReason());
|
||||||
|
assertNull(breakEvent.getDenialReason());
|
||||||
|
verify(placeTrigger, never()).setCancelled(anyBoolean());
|
||||||
|
verify(breakTrigger, never()).setCancelled(anyBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void noActiveEmergencyKeepsGriefPreventionDenial() {
|
||||||
|
Block block = blockAt(mock(Location.class));
|
||||||
|
BlockPlaceEvent trigger = mock(BlockPlaceEvent.class);
|
||||||
|
when(trigger.getBlock()).thenReturn(block);
|
||||||
|
when(permissions.hasBuildAccess(player)).thenReturn(true);
|
||||||
|
when(emergencies.activeSite()).thenReturn(null);
|
||||||
|
|
||||||
|
ClaimPermissionCheckEvent event = deniedEvent(ClaimPermission.Build, trigger, INCIDENT_CLAIM_ID);
|
||||||
|
bridge.onClaimPermissionCheck(event);
|
||||||
|
|
||||||
|
assertNotNull(event.getDenialReason());
|
||||||
|
assertTrue(event.isCancelled());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void nonFirefighterOrOtherwiseIneligiblePlayerKeepsDenial() {
|
||||||
|
Location location = mock(Location.class);
|
||||||
|
Block block = blockAt(location);
|
||||||
|
BlockPlaceEvent trigger = mock(BlockPlaceEvent.class);
|
||||||
|
when(trigger.getBlock()).thenReturn(block);
|
||||||
|
when(permissions.hasBuildAccess(player)).thenReturn(false);
|
||||||
|
when(emergencies.activeSite()).thenReturn(incidentSite);
|
||||||
|
|
||||||
|
ClaimPermissionCheckEvent event = deniedEvent(ClaimPermission.Build, trigger, INCIDENT_CLAIM_ID);
|
||||||
|
bridge.onClaimPermissionCheck(event);
|
||||||
|
|
||||||
|
assertNotNull(event.getDenialReason());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void unrelatedOrChildClaimKeepsDenialEvenInsideResponseRadius() {
|
||||||
|
Location location = mock(Location.class);
|
||||||
|
Block block = blockAt(location);
|
||||||
|
BlockPlaceEvent trigger = mock(BlockPlaceEvent.class);
|
||||||
|
when(trigger.getBlock()).thenReturn(block);
|
||||||
|
allowEligibleIncident(location);
|
||||||
|
|
||||||
|
ClaimPermissionCheckEvent unrelatedClaim = deniedEvent(ClaimPermission.Build, trigger, 99L);
|
||||||
|
ClaimPermissionCheckEvent childClaim = deniedEvent(ClaimPermission.Build, trigger, 43L);
|
||||||
|
|
||||||
|
bridge.onClaimPermissionCheck(unrelatedClaim);
|
||||||
|
bridge.onClaimPermissionCheck(childClaim);
|
||||||
|
|
||||||
|
assertNotNull(unrelatedClaim.getDenialReason());
|
||||||
|
assertNotNull(childClaim.getDenialReason());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void incidentEndImmediatelyRestoresDenial() {
|
||||||
|
Location location = mock(Location.class);
|
||||||
|
Block block = blockAt(location);
|
||||||
|
BlockPlaceEvent trigger = mock(BlockPlaceEvent.class);
|
||||||
|
when(trigger.getBlock()).thenReturn(block);
|
||||||
|
|
||||||
|
allowEligibleIncident(location);
|
||||||
|
ClaimPermissionCheckEvent duringIncident = deniedEvent(ClaimPermission.Build, trigger, INCIDENT_CLAIM_ID);
|
||||||
|
bridge.onClaimPermissionCheck(duringIncident);
|
||||||
|
assertNull(duringIncident.getDenialReason());
|
||||||
|
|
||||||
|
when(permissions.hasBuildAccess(player)).thenReturn(false);
|
||||||
|
when(emergencies.activeSite()).thenReturn(null);
|
||||||
|
ClaimPermissionCheckEvent afterIncident = deniedEvent(ClaimPermission.Build, trigger, INCIDENT_CLAIM_ID);
|
||||||
|
bridge.onClaimPermissionCheck(afterIncident);
|
||||||
|
|
||||||
|
assertNotNull(afterIncident.getDenialReason());
|
||||||
|
assertTrue(afterIncident.isCancelled());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void onlyBuildPermissionIsBypassed() {
|
||||||
|
Location location = mock(Location.class);
|
||||||
|
Block block = blockAt(location);
|
||||||
|
BlockPlaceEvent trigger = mock(BlockPlaceEvent.class);
|
||||||
|
when(trigger.getBlock()).thenReturn(block);
|
||||||
|
allowEligibleIncident(location);
|
||||||
|
|
||||||
|
ClaimPermissionCheckEvent editEvent = deniedEvent(ClaimPermission.Edit, trigger, INCIDENT_CLAIM_ID);
|
||||||
|
bridge.onClaimPermissionCheck(editEvent);
|
||||||
|
|
||||||
|
assertNotNull(editEvent.getDenialReason());
|
||||||
|
assertTrue(editEvent.isCancelled());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void listenerExplicitlyReceivesCancelledPermissionChecksAtHighestPriority() throws Exception {
|
||||||
|
Method method = GriefPreventionBridge.class.getMethod("onClaimPermissionCheck", ClaimPermissionCheckEvent.class);
|
||||||
|
EventHandler handler = method.getAnnotation(EventHandler.class);
|
||||||
|
|
||||||
|
assertNotNull(handler);
|
||||||
|
assertEquals(EventPriority.HIGHEST, handler.priority());
|
||||||
|
assertFalse(handler.ignoreCancelled());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void allowEligibleIncident(Location location) {
|
||||||
|
when(permissions.hasBuildAccess(player)).thenReturn(true);
|
||||||
|
when(emergencies.activeSite()).thenReturn(incidentSite);
|
||||||
|
when(permissions.isInsideActiveSite(location)).thenReturn(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Block blockAt(Location location) {
|
||||||
|
Block block = mock(Block.class);
|
||||||
|
when(block.getLocation()).thenReturn(location);
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClaimPermissionCheckEvent deniedEvent(ClaimPermission permission, org.bukkit.event.Event trigger, Long claimId) {
|
||||||
|
Claim claim = mock(Claim.class);
|
||||||
|
when(claim.getID()).thenReturn(claimId);
|
||||||
|
ClaimPermissionCheckEvent event = new ClaimPermissionCheckEvent(player, claim, permission, trigger);
|
||||||
|
event.setDenialReason(DENIED);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user