diff --git a/src/main/java/dev/esophose/playerparticles/hook/WorldGuardHook.java b/src/main/java/dev/esophose/playerparticles/hook/WorldGuardHook.java index a3b8759..3c034ce 100644 --- a/src/main/java/dev/esophose/playerparticles/hook/WorldGuardHook.java +++ b/src/main/java/dev/esophose/playerparticles/hook/WorldGuardHook.java @@ -1,9 +1,10 @@ package dev.esophose.playerparticles.hook; +import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; +import java.util.Set; import org.bukkit.Bukkit; import org.bukkit.Location; import org.codemc.worldguardwrapper.WorldGuardWrapper; @@ -38,36 +39,89 @@ public static boolean enabled() { } /** - * Checks if a location is in a region that allows particles to spawn + * Result of a combined region status check. + * Avoids calling getRegions() twice for each player. + */ + public static class RegionStatus { + public final boolean allowed; + public final boolean limited; + + public RegionStatus(boolean allowed, boolean limited) { + this.allowed = allowed; + this.limited = limited; + } + } + + /** + * Checks both allowed and limited region flags in a single pass. + * This is more efficient than calling isInAllowedRegion() and isInLimitedRegion() separately + * because it only fetches and sorts regions once. * * @param location The location to check - * @return true if the location is in an allowed region, otherwise false + * @return A RegionStatus containing both allowed and limited states */ @SuppressWarnings("unchecked") - public static boolean isInAllowedRegion(Location location) { - if (!enabled() || flagPlayerParticles == null) - return true; - - List regions = worldGuardWrapper.getRegions(location).stream() - .sorted(Comparator.comparing(IWrappedRegion::getPriority)) - .collect(Collectors.toList()); - - // Get the "player-particles" flag. - // This will use the region priority to determine which one takes precedence. - for (IWrappedRegion region : regions) { - Optional flagState = region.getFlag(flagPlayerParticles); - if (flagState.isPresent()) { - Object value = flagState.get(); - // Fix a weird mismatch where the type in the compiler does not match the runtime type - if (value instanceof WrappedState && value == WrappedState.DENY) { - return false; - } else if (value instanceof Optional && ((Optional) value).get() == WrappedState.DENY) { - return false; + public static RegionStatus getRegionStatuses(Location location) { + boolean allowed = true; + boolean limited = false; + + if (!enabled()) + return new RegionStatus(allowed, limited); + + // Only fetch regions if at least one flag is registered + if (flagPlayerParticles == null && flagPlayerParticlesLimited == null) + return new RegionStatus(allowed, limited); + + // Fetch regions once and sort without streams to avoid unnecessary allocations + Set regionSet = worldGuardWrapper.getRegions(location); + List regions = new ArrayList<>(regionSet); + regions.sort(Comparator.comparingInt(IWrappedRegion::getPriority)); + + // Check "player-particles" flag + if (flagPlayerParticles != null) { + for (IWrappedRegion region : regions) { + Optional flagState = region.getFlag(flagPlayerParticles); + if (flagState.isPresent()) { + Object value = flagState.get(); + if (value instanceof WrappedState && value == WrappedState.DENY) { + allowed = false; + break; + } else if (value instanceof Optional && ((Optional) value).get() == WrappedState.DENY) { + allowed = false; + break; + } + } + } + } + + // Check "player-particles-limited" flag + if (flagPlayerParticlesLimited != null) { + for (IWrappedRegion region : regions) { + Optional flagState = region.getFlag(flagPlayerParticlesLimited); + if (flagState.isPresent()) { + Object value = flagState.get(); + if (value instanceof WrappedState && value == WrappedState.DENY) { + limited = true; + break; + } else if (value instanceof Optional && ((Optional) value).get() == WrappedState.DENY) { + limited = true; + break; + } } } } - return true; + return new RegionStatus(allowed, limited); + } + + /** + * Checks if a location is in a region that allows particles to spawn + * + * @param location The location to check + * @return true if the location is in an allowed region, otherwise false + */ + public static boolean isInAllowedRegion(Location location) { + return getRegionStatuses(location).allowed; } /** @@ -77,27 +131,7 @@ public static boolean isInAllowedRegion(Location location) { * @return true if the location only allows limited particles, otherwise false */ public static boolean isInLimitedRegion(Location location) { - if (!enabled() || flagPlayerParticlesLimited == null) - return false; - - List regions = worldGuardWrapper.getRegions(location).stream() - .sorted(Comparator.comparing(IWrappedRegion::getPriority)) - .collect(Collectors.toList()); - - for (IWrappedRegion region : regions) { - Optional flagState = region.getFlag(flagPlayerParticlesLimited); - if (flagState.isPresent()) { - Object value = flagState.get(); - // Fix a weird mismatch where the type in the compiler does not match the runtime type - if (value instanceof WrappedState && value == WrappedState.DENY) { - return true; - } else if (value instanceof Optional && ((Optional) value).get() == WrappedState.DENY) { - return true; - } - } - } - - return false; + return getRegionStatuses(location).limited; } } diff --git a/src/main/java/dev/esophose/playerparticles/manager/ParticleManager.java b/src/main/java/dev/esophose/playerparticles/manager/ParticleManager.java index ecaf9d6..0ff5af1 100644 --- a/src/main/java/dev/esophose/playerparticles/manager/ParticleManager.java +++ b/src/main/java/dev/esophose/playerparticles/manager/ParticleManager.java @@ -212,7 +212,15 @@ public void run() { } /** - * Updates the WorldGuard region statuses for players + * The minimum distance squared a player must move before WorldGuard regions are re-checked. + * Using squared distance to avoid expensive sqrt calculations. + * 1 block = 1.0 distance squared threshold. + */ + private static final double WORLDGUARD_RECHECK_DISTANCE_SQ = 1.0; + + /** + * Updates the WorldGuard region statuses for players. + * Skips players who haven't moved significantly since the last check. */ private void updateWorldGuardStatuses() { PermissionManager permissionManager = this.rosePlugin.getManager(PermissionManager.class); @@ -222,14 +230,28 @@ private void updateWorldGuardStatuses() { if (player == null) continue; + Location currentLocation = player.getLocation(); + + // Skip the check if the player hasn't moved significantly since the last check + Location lastLocation = pplayer.getLastWorldGuardLocation(); + if (lastLocation != null + && lastLocation.getWorld() == currentLocation.getWorld() + && lastLocation.distanceSquared(currentLocation) < WORLDGUARD_RECHECK_DISTANCE_SQ) + continue; + + // Update the stored location + pplayer.setLastWorldGuardLocation(currentLocation); + boolean inAllowedRegion; boolean inLimitedRegion; if (Settings.WORLDGUARD_ENABLE_BYPASS_PERMISSION.get() && permissionManager.hasWorldGuardBypass(player)) { inAllowedRegion = true; inLimitedRegion = false; } else { - inAllowedRegion = WorldGuardHook.isInAllowedRegion(player.getLocation()); - inLimitedRegion = WorldGuardHook.isInLimitedRegion(player.getLocation()); + // Use the combined method to fetch regions only once + WorldGuardHook.RegionStatus status = WorldGuardHook.getRegionStatuses(currentLocation); + inAllowedRegion = status.allowed; + inLimitedRegion = status.limited; } pplayer.setInAllowedRegion(inAllowedRegion); diff --git a/src/main/java/dev/esophose/playerparticles/particles/PPlayer.java b/src/main/java/dev/esophose/playerparticles/particles/PPlayer.java index 8c2e1b1..5c58cdb 100644 --- a/src/main/java/dev/esophose/playerparticles/particles/PPlayer.java +++ b/src/main/java/dev/esophose/playerparticles/particles/PPlayer.java @@ -12,6 +12,7 @@ import java.util.UUID; import java.util.stream.Collectors; import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -67,6 +68,12 @@ public class PPlayer { */ private boolean inLimitedRegion; + /** + * The last location used for WorldGuard region checks. + * Used to skip redundant checks when the player hasn't moved significantly. + */ + private Location lastWorldGuardLocation; + /** * Constructs a new PPlayer * @@ -233,6 +240,24 @@ public boolean isInLimitedRegion() { return this.inLimitedRegion; } + /** + * Gets the last location used for WorldGuard region checks + * + * @return The last checked location, or null if never checked + */ + public Location getLastWorldGuardLocation() { + return this.lastWorldGuardLocation; + } + + /** + * Sets the last location used for WorldGuard region checks + * + * @param location The location to store + */ + public void setLastWorldGuardLocation(Location location) { + this.lastWorldGuardLocation = location; + } + /** * Gets a ParticleGroup this player has by its name *