diff --git a/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java b/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java index 25a817993f4..c0c46d909dc 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java +++ b/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java @@ -1230,10 +1230,12 @@ private final class PickupListenerPre1_12 implements Listener { public void onPlayerPickupItem(final org.bukkit.event.player.PlayerPickupItemEvent event) { if (event.getItem().hasMetadata(Commandfireball.FIREBALL_META_KEY)) { event.setCancelled(true); - } else if (ess.getSettings().getDisableItemPickupWhileAfk()) { - if (ess.getUser(event.getPlayer()).isAfk()) { - event.setCancelled(true); - } + return; + } + final User user = ess.getUser(event.getPlayer()); + if ((ess.getSettings().getDisableItemPickupWhileAfk() && user.isAfk()) + || (user.isVanished() && !user.isAuthorizedCached("essentials.vanish.pickup"))) { + event.setCancelled(true); } } } @@ -1241,8 +1243,10 @@ public void onPlayerPickupItem(final org.bukkit.event.player.PlayerPickupItemEve private final class PickupListener1_12 implements Listener { @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public void onPlayerPickupItem(final org.bukkit.event.entity.EntityPickupItemEvent event) { - if (ess.getSettings().getDisableItemPickupWhileAfk() && event.getEntity() instanceof Player) { - if (ess.getUser((Player) event.getEntity()).isAfk()) { + if (event.getEntity() instanceof Player) { + final User user = ess.getUser((Player) event.getEntity()); + if ((ess.getSettings().getDisableItemPickupWhileAfk() && user.isAfk()) + || (user.isVanished() && !user.isAuthorizedCached("essentials.vanish.pickup"))) { event.setCancelled(true); } } @@ -1261,6 +1265,10 @@ public void onGameEvent(final org.bukkit.event.block.BlockReceiveGameEvent event private final class CommandSendFilter implements CommandSendListenerProvider.Filter { @Override public Predicate apply(Player player) { + // There is no event for op status changes, but the command list is resent when + // a player is opped/deopped, so we invalidate cached permissions here. + ess.getPermissionsHandler().invalidatePermissionCache(player.getUniqueId()); + final User user = ess.getUser(player); final Set checked = new HashSet<>(); final Set toRemove = new HashSet<>(); diff --git a/Essentials/src/main/java/com/earth2me/essentials/IUser.java b/Essentials/src/main/java/com/earth2me/essentials/IUser.java index a701eb5610f..bb427dcba16 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/IUser.java +++ b/Essentials/src/main/java/com/earth2me/essentials/IUser.java @@ -32,6 +32,8 @@ public interface IUser { boolean isAuthorized(String node); + boolean isAuthorizedCached(String node); + boolean isAuthorized(IEssentialsCommand cmd); boolean isAuthorized(IEssentialsCommand cmd, String permissionPrefix); diff --git a/Essentials/src/main/java/com/earth2me/essentials/User.java b/Essentials/src/main/java/com/earth2me/essentials/User.java index 36a2df5ffc1..9b039bf21e6 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/User.java +++ b/Essentials/src/main/java/com/earth2me/essentials/User.java @@ -147,6 +147,19 @@ public boolean isAuthorized(final String node) { return result; } + @Override + public boolean isAuthorizedCached(final String node) { + if (Essentials.TESTING) { + return false; + } + + final boolean result = isAuthorizedCachedCheck(node); + if (ess.getSettings().isDebug()) { + ess.getLogger().log(Level.INFO, "checking if " + base.getName() + " has " + node + " (cached) - " + result); + } + return result; + } + @Override public boolean isPermissionSet(final String node) { if (Essentials.TESTING) { @@ -186,6 +199,24 @@ private boolean isAuthorizedCheck(final String node) { } } + private boolean isAuthorizedCachedCheck(final String node) { + if (base instanceof OfflinePlayerStub) { + return false; + } + + try { + return ess.getPermissionsHandler().hasPermissionCached(base, node); + } catch (final Exception ex) { + if (ess.getSettings().isDebug()) { + ess.getLogger().log(Level.SEVERE, "Permission System Error: " + ess.getPermissionsHandler().getName() + " returned: " + ex.getMessage(), ex); + } else { + ess.getLogger().log(Level.SEVERE, "Permission System Error: " + ess.getPermissionsHandler().getName() + " returned: " + ex.getMessage()); + } + + return false; + } + } + private boolean isPermSetCheck(final String node) { if (base instanceof OfflinePlayerStub) { return false; @@ -872,7 +903,7 @@ public void checkActivity() { } } final long autoafk = ess.getSettings().getAutoAfk(); - if (!isAfk() && autoafk > 0 && lastActivity + autoafk * 1000 < System.currentTimeMillis() && isAuthorized("essentials.afk.auto")) { + if (!isAfk() && autoafk > 0 && lastActivity + autoafk * 1000 < System.currentTimeMillis() && isAuthorizedCached("essentials.afk.auto")) { setAfk(true, AfkStatusChangeEvent.Cause.ACTIVITY); if (isAfk() && !isHidden()) { setDisplayNick(); diff --git a/Essentials/src/main/java/com/earth2me/essentials/perm/IPermissionsHandler.java b/Essentials/src/main/java/com/earth2me/essentials/perm/IPermissionsHandler.java index 76d47a2e160..6b440918050 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/perm/IPermissionsHandler.java +++ b/Essentials/src/main/java/com/earth2me/essentials/perm/IPermissionsHandler.java @@ -28,6 +28,13 @@ public interface IPermissionsHandler { boolean hasPermission(Player base, String node); + default boolean hasPermissionCached(Player base, String node) { + return hasPermission(base, node); + } + + default void invalidatePermissionCache(UUID uuid) { + } + // Does not check for * permissions boolean isPermissionSet(Player base, String node); diff --git a/Essentials/src/main/java/com/earth2me/essentials/perm/PermissionsHandler.java b/Essentials/src/main/java/com/earth2me/essentials/perm/PermissionsHandler.java index 0cbad14775b..b5c15cb051a 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/perm/PermissionsHandler.java +++ b/Essentials/src/main/java/com/earth2me/essentials/perm/PermissionsHandler.java @@ -105,6 +105,16 @@ public boolean hasPermission(final Player base, final String node) { return handler.hasPermission(base, node); } + @Override + public boolean hasPermissionCached(final Player base, final String node) { + return handler.hasPermissionCached(base, node); + } + + @Override + public void invalidatePermissionCache(final UUID uuid) { + handler.invalidatePermissionCache(uuid); + } + @Override public boolean isPermissionSet(final Player base, final String node) { return handler.isPermissionSet(base, node); diff --git a/Essentials/src/main/java/com/earth2me/essentials/perm/impl/LuckPermsHandler.java b/Essentials/src/main/java/com/earth2me/essentials/perm/impl/LuckPermsHandler.java index 1dc9418e75b..a3d1d0143ca 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/perm/impl/LuckPermsHandler.java +++ b/Essentials/src/main/java/com/earth2me/essentials/perm/impl/LuckPermsHandler.java @@ -7,17 +7,22 @@ import net.luckperms.api.context.ContextConsumer; import net.luckperms.api.context.ContextSet; import net.luckperms.api.context.ImmutableContextSet; +import net.luckperms.api.event.EventSubscription; +import net.luckperms.api.event.user.UserDataRecalculateEvent; import net.luckperms.api.model.group.Group; import net.luckperms.api.query.QueryOptions; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.plugin.RegisteredServiceProvider; +import java.util.logging.Level; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.function.Supplier; @@ -25,6 +30,8 @@ public class LuckPermsHandler extends ModernVaultHandler { private LuckPerms luckPerms; private Essentials ess; private CombinedCalculator calculator; + private final Map> permissionCache = new ConcurrentHashMap<>(); + private EventSubscription recalculateSubscription; @Override public void registerContext(final String context, final Function> calculator, final Supplier> suggestions) { @@ -41,6 +48,30 @@ public void unregisterContexts() { this.luckPerms.getContextManager().unregisterCalculator(this.calculator); this.calculator = null; } + if (this.recalculateSubscription != null) { + this.recalculateSubscription.close(); + this.recalculateSubscription = null; + } + this.permissionCache.clear(); + } + + @Override + public boolean hasPermissionCached(final Player base, final String node) { + final UUID uuid = base.getUniqueId(); + final Map userCache = permissionCache.computeIfAbsent(uuid, k -> new ConcurrentHashMap<>()); + return userCache.computeIfAbsent(node, k -> hasPermission(base, node)); + } + + @Override + public void invalidatePermissionCache(final UUID uuid) { + invalidateCache(uuid); + } + + public void invalidateCache(final UUID uuid) { + if (ess.getSettings().isDebug()) { + ess.getLogger().log(Level.INFO, "Invalidating permission cache for " + uuid); + } + permissionCache.remove(uuid); } @Override @@ -77,6 +108,11 @@ public boolean tryProvider(Essentials ess) { if (provider != null) { this.luckPerms = provider.getProvider(); this.ess = ess; + this.recalculateSubscription = this.luckPerms.getEventBus().subscribe( + ess, + UserDataRecalculateEvent.class, + event -> invalidateCache(event.getUser().getUniqueId()) + ); } return luckPerms != null && super.tryProvider(ess); } diff --git a/Essentials/src/main/resources/plugin.yml b/Essentials/src/main/resources/plugin.yml index 4c578304f3e..b18e7f06f6c 100644 --- a/Essentials/src/main/resources/plugin.yml +++ b/Essentials/src/main/resources/plugin.yml @@ -1374,6 +1374,9 @@ permissions: description: Applies invisibility effects to the player when they are in vanish mode essentials.vanish.interact: description: Allows the bearer to interact with players in vanish mode + essentials.vanish.pickup: + description: Allows the bearer to pick up items while in vanish mode + default: false essentials.version: description: Allows access to the /version command essentials.warp: