Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 78 additions & 44 deletions src/main/java/dev/esophose/playerparticles/hook/WorldGuardHook.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<IWrappedRegion> 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<WrappedState> 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<WrappedState>) 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<IWrappedRegion> regionSet = worldGuardWrapper.getRegions(location);
List<IWrappedRegion> regions = new ArrayList<>(regionSet);
regions.sort(Comparator.comparingInt(IWrappedRegion::getPriority));

// Check "player-particles" flag
if (flagPlayerParticles != null) {
for (IWrappedRegion region : regions) {
Optional<WrappedState> 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<WrappedState>) value).get() == WrappedState.DENY) {
allowed = false;
break;
}
}
}
}

// Check "player-particles-limited" flag
if (flagPlayerParticlesLimited != null) {
for (IWrappedRegion region : regions) {
Optional<WrappedState> 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<WrappedState>) 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;
}

/**
Expand All @@ -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<IWrappedRegion> regions = worldGuardWrapper.getRegions(location).stream()
.sorted(Comparator.comparing(IWrappedRegion::getPriority))
.collect(Collectors.toList());

for (IWrappedRegion region : regions) {
Optional<WrappedState> 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<WrappedState>) value).get() == WrappedState.DENY) {
return true;
}
}
}

return false;
return getRegionStatuses(location).limited;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/dev/esophose/playerparticles/particles/PPlayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
*
Expand Down Expand Up @@ -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
*
Expand Down