diff --git a/api/src/main/java/me/lucko/luckperms/api/platform/PlatformType.java b/api/src/main/java/me/lucko/luckperms/api/platform/PlatformType.java
index 48d8ef3e0..c66ffa5f8 100644
--- a/api/src/main/java/me/lucko/luckperms/api/platform/PlatformType.java
+++ b/api/src/main/java/me/lucko/luckperms/api/platform/PlatformType.java
@@ -36,7 +36,8 @@ public enum PlatformType {
BUKKIT("Bukkit"),
BUNGEE("Bungee"),
- SPONGE("Sponge");
+ SPONGE("Sponge"),
+ NUKKIT("Nukkit");
private final String friendlyName;
diff --git a/bukkit/pom.xml b/bukkit/pom.xml
index 9db25c6b3..cbe9bc382 100644
--- a/bukkit/pom.xml
+++ b/bukkit/pom.xml
@@ -244,4 +244,12 @@
+
+
+
+ spigot-repo
+ https://hub.spigotmc.org/nexus/content/repositories/snapshots/
+
+
+
diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/server/LPDefaultsMap.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/server/LPDefaultsMap.java
index c434bb124..8654adba2 100644
--- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/server/LPDefaultsMap.java
+++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/server/LPDefaultsMap.java
@@ -55,7 +55,7 @@ import javax.annotation.Nonnull;
*
* Injected by {@link InjectorDefaultsMap}.
*/
-public class LPDefaultsMap implements Map> {
+public final class LPDefaultsMap implements Map> {
// keyset for all instances
private static final Set KEY_SET = ImmutableSet.of(Boolean.TRUE, Boolean.FALSE);
diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/server/LPPermissionMap.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/server/LPPermissionMap.java
index 3998c94fd..f9bb01627 100644
--- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/server/LPPermissionMap.java
+++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/server/LPPermissionMap.java
@@ -56,7 +56,7 @@ import javax.annotation.Nonnull;
*
* Injected by {@link InjectorPermissionMap}.
*/
-public class LPPermissionMap extends ForwardingMap {
+public final class LPPermissionMap extends ForwardingMap {
// Uses perm.getName().toLowerCase(java.util.Locale.ENGLISH); to determine the key
private final Map delegate = new ConcurrentHashMap<>();
diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/server/LPSubscriptionMap.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/server/LPSubscriptionMap.java
index 64366f16f..69499f211 100644
--- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/server/LPSubscriptionMap.java
+++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/server/LPSubscriptionMap.java
@@ -33,7 +33,6 @@ import me.lucko.luckperms.common.utils.ImmutableCollectors;
import org.bukkit.entity.Player;
import org.bukkit.permissions.Permissible;
-import org.bukkit.permissions.Permission;
import org.bukkit.plugin.PluginManager;
import java.util.Collection;
@@ -56,7 +55,7 @@ import javax.annotation.Nonnull;
*
* Bukkit for some reason sometimes uses subscription status to determine whether
* a permissible has a given node, instead of checking directly with
- * {@link Permissible#hasPermission(Permission)}.
+ * {@link Permissible#hasPermission(String)}.
*
* {@link org.bukkit.Server#broadcast(String, String)} is a good example of this.
*
@@ -68,7 +67,7 @@ import javax.annotation.Nonnull;
*
* Injected by {@link InjectorSubscriptionMap}.
*/
-public class LPSubscriptionMap extends HashMap> {
+public final class LPSubscriptionMap extends HashMap> {
// the plugin instance
final LPBukkitPlugin plugin;
@@ -151,7 +150,7 @@ public class LPSubscriptionMap extends HashMap
/**
* Value map extension which includes LP objects in Permissible related queries.
*/
- public class LPSubscriptionValueMap implements Map {
+ public final class LPSubscriptionValueMap implements Map {
// the permission being mapped to this value map
private final String permission;
@@ -159,7 +158,7 @@ public class LPSubscriptionMap extends HashMap
// the backing map
private final Map backing;
- public LPSubscriptionValueMap(String permission, Map backing) {
+ private LPSubscriptionValueMap(String permission, Map backing) {
this.permission = permission;
this.backing = backing;
}
@@ -235,6 +234,11 @@ public class LPSubscriptionMap extends HashMap
return false;
}
+ @Override
+ public int size() {
+ return Math.max(1, this.backing.size());
+ }
+
// just delegate to the backing map
@Override
@@ -247,13 +251,6 @@ public class LPSubscriptionMap extends HashMap
return this.backing.remove(key);
}
- // the following methods are not used in the current impls of PluginManager, but just delegate them for now
-
- @Override
- public int size() {
- return this.backing.size();
- }
-
@Override
public boolean containsValue(Object value) {
return this.backing.containsValue(value);
diff --git a/common/src/main/java/me/lucko/luckperms/common/config/ConfigKeys.java b/common/src/main/java/me/lucko/luckperms/common/config/ConfigKeys.java
index 6a6b58fab..f3c85a193 100644
--- a/common/src/main/java/me/lucko/luckperms/common/config/ConfigKeys.java
+++ b/common/src/main/java/me/lucko/luckperms/common/config/ConfigKeys.java
@@ -208,6 +208,21 @@ public class ConfigKeys {
*/
public static final ConfigKey APPLY_BUKKIT_ATTACHMENT_PERMISSIONS = EnduringKey.wrap(BooleanKey.of("apply-bukkit-attachment-permissions", true));
+ /**
+ * If Nukkit child permissions are being applied. This setting is ignored on other platforms.
+ */
+ public static final ConfigKey APPLY_NUKKIT_CHILD_PERMISSIONS = EnduringKey.wrap(BooleanKey.of("apply-nukkit-child-permissions", true));
+
+ /**
+ * If Nukkit default permissions are being applied. This setting is ignored on other platforms.
+ */
+ public static final ConfigKey APPLY_NUKKIT_DEFAULT_PERMISSIONS = EnduringKey.wrap(BooleanKey.of("apply-nukkit-default-permissions", true));
+
+ /**
+ * If Nukkit attachment permissions are being applied. This setting is ignored on other platforms.
+ */
+ public static final ConfigKey APPLY_NUKKIT_ATTACHMENT_PERMISSIONS = EnduringKey.wrap(BooleanKey.of("apply-nukkit-attachment-permissions", true));
+
/**
* If BungeeCord configured permissions are being applied. This setting is ignored on other platforms.
*/
diff --git a/nukkit/pom.xml b/nukkit/pom.xml
new file mode 100644
index 000000000..ad5f48393
--- /dev/null
+++ b/nukkit/pom.xml
@@ -0,0 +1,156 @@
+
+
+
+ luckperms
+ me.lucko.luckperms
+ 4.0-SNAPSHOT
+
+ 4.0.0
+
+ luckperms-nukkit
+ jar
+
+
+ clean package
+ LuckPerms-Nukkit-${full.version}
+
+
+ src/main/resources
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${compiler.version}
+
+ 1.8
+ 1.8
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ ${shade.version}
+
+
+ package
+
+ shade
+
+
+ false
+ false
+
+
+
+ net.kyori.text
+ me.lucko.luckperms.lib.text
+
+
+
+
+ com.github.benmanes.caffeine
+ me.lucko.luckperms.lib.caffeine
+
+
+ okio
+ me.lucko.luckperms.lib.okio
+
+
+ okhttp3
+ me.lucko.luckperms.lib.okhttp3
+
+
+ org.mariadb.jdbc
+ me.lucko.luckperms.lib.mariadb
+
+
+ com.mysql
+ me.lucko.luckperms.lib.mysql
+
+
+ org.postgresql
+ me.lucko.luckperms.lib.postgresql
+
+
+ com.zaxxer.hikari
+ me.lucko.luckperms.lib.hikari
+
+
+ com.mongodb
+ me.lucko.luckperms.lib.mongodb
+
+
+ org.bson
+ me.lucko.luckperms.lib.bson
+
+
+ redis.clients.jedis
+ me.lucko.luckperms.lib.jedis
+
+
+ org.apache.commons.pool2
+ me.lucko.luckperms.lib.commonspool2
+
+
+ ninja.leaping.configurate
+ me.lucko.luckperms.lib.configurate
+
+
+ com.typesafe.config
+ me.lucko.luckperms.lib.hocon
+
+
+
+
+
+
+
+
+
+
+
+
+ me.lucko.luckperms
+ luckperms-common
+ ${project.version}
+ compile
+
+
+
+
+ com.google.code.findbugs
+ jsr305
+ 3.0.2
+ provided
+
+
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+ 2.6.1
+ provided
+
+
+
+
+ cn.nukkit
+ nukkit
+ 1.0-SNAPSHOT
+ provided
+
+
+
+
+
+ nukkit-repo
+ https://repo.potestas.xyz/main/
+
+
+
+
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/LPNukkitPlugin.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/LPNukkitPlugin.java
new file mode 100644
index 000000000..60fbfeb76
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/LPNukkitPlugin.java
@@ -0,0 +1,634 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit;
+
+import me.lucko.luckperms.api.Contexts;
+import me.lucko.luckperms.api.LuckPermsApi;
+import me.lucko.luckperms.api.platform.PlatformType;
+import me.lucko.luckperms.common.actionlog.LogDispatcher;
+import me.lucko.luckperms.common.api.ApiRegistrationUtil;
+import me.lucko.luckperms.common.api.LuckPermsApiProvider;
+import me.lucko.luckperms.common.buffers.BufferedRequest;
+import me.lucko.luckperms.common.buffers.UpdateTaskBuffer;
+import me.lucko.luckperms.common.caching.handlers.CachedStateManager;
+import me.lucko.luckperms.common.calculators.CalculatorFactory;
+import me.lucko.luckperms.common.commands.CommandPermission;
+import me.lucko.luckperms.common.commands.sender.Sender;
+import me.lucko.luckperms.common.config.AbstractConfiguration;
+import me.lucko.luckperms.common.config.ConfigKeys;
+import me.lucko.luckperms.common.config.LuckPermsConfiguration;
+import me.lucko.luckperms.common.contexts.ContextManager;
+import me.lucko.luckperms.common.contexts.LuckPermsCalculator;
+import me.lucko.luckperms.common.dependencies.DependencyManager;
+import me.lucko.luckperms.common.dependencies.DependencyRegistry;
+import me.lucko.luckperms.common.dependencies.classloader.PluginClassLoader;
+import me.lucko.luckperms.common.dependencies.classloader.ReflectionClassLoader;
+import me.lucko.luckperms.common.event.EventFactory;
+import me.lucko.luckperms.common.inheritance.InheritanceHandler;
+import me.lucko.luckperms.common.locale.LocaleManager;
+import me.lucko.luckperms.common.locale.NoopLocaleManager;
+import me.lucko.luckperms.common.locale.SimpleLocaleManager;
+import me.lucko.luckperms.common.logging.Logger;
+import me.lucko.luckperms.common.logging.SenderLogger;
+import me.lucko.luckperms.common.managers.group.StandardGroupManager;
+import me.lucko.luckperms.common.managers.track.StandardTrackManager;
+import me.lucko.luckperms.common.managers.user.StandardUserManager;
+import me.lucko.luckperms.common.messaging.InternalMessagingService;
+import me.lucko.luckperms.common.messaging.MessagingFactory;
+import me.lucko.luckperms.common.model.User;
+import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
+import me.lucko.luckperms.common.storage.Storage;
+import me.lucko.luckperms.common.storage.StorageFactory;
+import me.lucko.luckperms.common.storage.StorageType;
+import me.lucko.luckperms.common.storage.dao.file.FileWatcher;
+import me.lucko.luckperms.common.tasks.CacheHousekeepingTask;
+import me.lucko.luckperms.common.tasks.ExpireTemporaryTask;
+import me.lucko.luckperms.common.tasks.UpdateTask;
+import me.lucko.luckperms.common.treeview.PermissionVault;
+import me.lucko.luckperms.common.verbose.VerboseHandler;
+import me.lucko.luckperms.nukkit.calculators.NukkitCalculatorFactory;
+import me.lucko.luckperms.nukkit.contexts.NukkitContextManager;
+import me.lucko.luckperms.nukkit.contexts.WorldCalculator;
+import me.lucko.luckperms.nukkit.listeners.NukkitConnectionListener;
+import me.lucko.luckperms.nukkit.listeners.NukkitPlatformListener;
+import me.lucko.luckperms.nukkit.model.PermissionDefault;
+import me.lucko.luckperms.nukkit.model.permissible.LPPermissible;
+import me.lucko.luckperms.nukkit.model.permissible.PermissibleInjector;
+import me.lucko.luckperms.nukkit.model.permissible.PermissibleMonitoringInjector;
+import me.lucko.luckperms.nukkit.model.server.InjectorDefaultsMap;
+import me.lucko.luckperms.nukkit.model.server.InjectorPermissionMap;
+import me.lucko.luckperms.nukkit.model.server.InjectorSubscriptionMap;
+import me.lucko.luckperms.nukkit.model.server.LPDefaultsMap;
+import me.lucko.luckperms.nukkit.model.server.LPPermissionMap;
+import me.lucko.luckperms.nukkit.model.server.LPSubscriptionMap;
+
+import cn.nukkit.Player;
+import cn.nukkit.command.PluginCommand;
+import cn.nukkit.event.HandlerList;
+import cn.nukkit.permission.Permission;
+import cn.nukkit.plugin.PluginBase;
+import cn.nukkit.plugin.PluginManager;
+import cn.nukkit.plugin.service.ServicePriority;
+import cn.nukkit.utils.Config;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Stream;
+
+import javax.annotation.Nullable;
+
+/**
+ * LuckPerms implementation for the Nukkit API.
+ */
+public class LPNukkitPlugin extends PluginBase implements LuckPermsPlugin {
+
+ private long startTime;
+ private NukkitSchedulerAdapter scheduler;
+ private NukkitCommandExecutor commandManager;
+ private LuckPermsConfiguration configuration;
+ private StandardUserManager userManager;
+ private StandardGroupManager groupManager;
+ private StandardTrackManager trackManager;
+ private Storage storage;
+ private FileWatcher fileWatcher = null;
+ private InternalMessagingService messagingService = null;
+ private LuckPermsApiProvider apiProvider;
+ private EventFactory eventFactory;
+ private Logger log;
+ private LPSubscriptionMap subscriptionMap;
+ private LPPermissionMap permissionMap;
+ private LPDefaultsMap defaultPermissionMap;
+ private LocaleManager localeManager;
+ private PluginClassLoader pluginClassLoader;
+ private DependencyManager dependencyManager;
+ private InheritanceHandler inheritanceHandler;
+ private CachedStateManager cachedStateManager;
+ private ContextManager contextManager;
+ private CalculatorFactory calculatorFactory;
+ private BufferedRequest updateTaskBuffer;
+ private VerboseHandler verboseHandler;
+ private NukkitSenderFactory senderFactory;
+ private PermissionVault permissionVault;
+ private LogDispatcher logDispatcher;
+ private Set uniqueConnections = ConcurrentHashMap.newKeySet();
+
+ @Override
+ public void onLoad() {
+ // setup minimal functionality in order to load initial dependencies
+ this.scheduler = new NukkitSchedulerAdapter(this);
+ this.localeManager = new NoopLocaleManager();
+ this.senderFactory = new NukkitSenderFactory(this);
+ this.log = new SenderLogger(this, getConsoleSender());
+
+ this.pluginClassLoader = new ReflectionClassLoader(this);
+ this.dependencyManager = new DependencyManager(this);
+ this.dependencyManager.loadDependencies(DependencyRegistry.GLOBAL_DEPENDENCIES);
+ }
+
+ @Override
+ public void onEnable() {
+ this.startTime = System.currentTimeMillis();
+ sendStartupBanner(getConsoleSender());
+ this.verboseHandler = new VerboseHandler(this.scheduler.asyncNukkit(), getVersion());
+ this.permissionVault = new PermissionVault(this.scheduler.asyncNukkit());
+ this.logDispatcher = new LogDispatcher(this);
+
+ getLog().info("Loading configuration...");
+ this.configuration = new AbstractConfiguration(this, new NukkitConfigAdapter(this, resolveConfig("config.yml")));
+ this.configuration.loadAll();
+
+ StorageFactory storageFactory = new StorageFactory(this);
+ Set storageTypes = storageFactory.getRequiredTypes(StorageType.H2);
+ this.dependencyManager.loadStorageDependencies(storageTypes);
+
+ // register events
+ NukkitConnectionListener connectionListener = new NukkitConnectionListener(this);
+ getServer().getPluginManager().registerEvents(connectionListener, this);
+ getServer().getPluginManager().registerEvents(new NukkitPlatformListener(this), this);
+
+ if (getConfiguration().get(ConfigKeys.WATCH_FILES)) {
+ this.fileWatcher = new FileWatcher(this);
+ getScheduler().asyncRepeating(this.fileWatcher, 30L);
+ }
+
+ // initialise datastore
+ this.storage = storageFactory.getInstance(StorageType.H2);
+
+ // initialise messaging
+ this.messagingService = new MessagingFactory<>(this).getInstance();
+
+ // setup the update task buffer
+ this.updateTaskBuffer = new UpdateTaskBuffer(this);
+
+ // load locale
+ this.localeManager = new SimpleLocaleManager();
+ this.localeManager.tryLoad(this, new File(getDataFolder(), "lang.yml"));
+
+ // register commands
+ this.commandManager = new NukkitCommandExecutor(this);
+ PluginCommand main = (PluginCommand) getServer().getPluginCommand("luckperms");
+ main.setExecutor(this.commandManager);
+
+ // load internal managers
+ getLog().info("Loading internal permission managers...");
+ this.inheritanceHandler = new InheritanceHandler(this);
+ this.userManager = new StandardUserManager(this);
+ this.groupManager = new StandardGroupManager(this);
+ this.trackManager = new StandardTrackManager(this);
+ this.calculatorFactory = new NukkitCalculatorFactory(this);
+ this.cachedStateManager = new CachedStateManager();
+
+ // setup context manager
+ this.contextManager = new NukkitContextManager(this);
+ this.contextManager.registerCalculator(new WorldCalculator(this));
+ this.contextManager.registerStaticCalculator(new LuckPermsCalculator(getConfiguration()));
+
+ // inject our own custom permission maps
+ Runnable[] injectors = new Runnable[]{
+ new InjectorSubscriptionMap(this),
+ new InjectorPermissionMap(this),
+ new InjectorDefaultsMap(this)
+ };
+
+ for (Runnable injector : injectors) {
+ injector.run();
+
+ // schedule another injection after all plugins have loaded
+ // the entire pluginmanager instance is replaced by some plugins :(
+ this.scheduler.asyncLater(injector, 1L);
+ }
+
+ // inject verbose handlers into internal nukkit objects
+ new PermissibleMonitoringInjector(this).run();
+
+ // register with the LP API
+ this.apiProvider = new LuckPermsApiProvider(this);
+
+ // setup event factory
+ this.eventFactory = new EventFactory(this, this.apiProvider);
+
+ ApiRegistrationUtil.registerProvider(this.apiProvider);
+ getServer().getServiceManager().register(LuckPermsApi.class, this.apiProvider, this, ServicePriority.NORMAL);
+
+
+ // schedule update tasks
+ int mins = getConfiguration().get(ConfigKeys.SYNC_TIME);
+ if (mins > 0) {
+ long ticks = mins * 60 * 20;
+ this.scheduler.asyncRepeating(() -> this.updateTaskBuffer.request(), ticks);
+ }
+ this.scheduler.asyncLater(() -> this.updateTaskBuffer.request(), 40L);
+
+ // run an update instantly.
+ getLog().info("Performing initial data load...");
+ try {
+ new UpdateTask(this, true).run();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+
+ // register tasks
+ this.scheduler.asyncRepeating(new ExpireTemporaryTask(this), 60L);
+ this.scheduler.asyncRepeating(new CacheHousekeepingTask(this), 2400L);
+
+ // register permissions
+ try {
+ PluginManager pm = getServer().getPluginManager();
+ PermissionDefault permDefault = getConfiguration().get(ConfigKeys.COMMANDS_ALLOW_OP) ? PermissionDefault.OP : PermissionDefault.FALSE;
+
+ for (CommandPermission p : CommandPermission.values()) {
+ pm.addPermission(new Permission(p.getPermission(), null, permDefault.toString()));
+ }
+ } catch (Exception e) {
+ // this throws an exception if the plugin is /reloaded, grr
+ }
+
+ if (!getConfiguration().get(ConfigKeys.OPS_ENABLED)) {
+ Config ops = getServer().getOps();
+ ops.getKeys(false).forEach(ops::remove);
+ }
+
+ // replace the temporary executor when the Nukkit one starts
+ getServer().getScheduler().scheduleTask(this, () -> this.scheduler.setUseFallback(false), true);
+
+ // Load any online users (in the case of a reload)
+ for (Player player : getServer().getOnlinePlayers().values()) {
+ this.scheduler.doAsync(() -> {
+ try {
+ User user = connectionListener.loadUser(player.getUniqueId(), player.getName());
+ if (user != null) {
+ this.scheduler.doSync(() -> {
+ try {
+ LPPermissible lpPermissible = new LPPermissible(player, user, this);
+ PermissibleInjector.inject(player, lpPermissible);
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ });
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+ }
+
+ getLog().info("Successfully enabled. (took " + (System.currentTimeMillis() - this.startTime) + "ms)");
+ }
+
+ @Override
+ public void onDisable() {
+ // Switch back to the fallback executor, the nukkit one won't allow new tasks
+ this.scheduler.setUseFallback(true);
+
+ this.permissionVault.shutdown();
+ this.verboseHandler.shutdown();
+
+ // uninject from players
+ for (Player player : getServer().getOnlinePlayers().values()) {
+ try {
+ PermissibleInjector.unInject(player, false);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ if (getConfiguration().get(ConfigKeys.AUTO_OP)) {
+ player.setOp(false);
+ }
+
+ final User user = getUserManager().getIfLoaded(player.getUniqueId());
+ if (user != null) {
+ user.getCachedData().invalidateCaches();
+ getUserManager().unload(user);
+ }
+ }
+
+ // uninject custom maps
+ InjectorSubscriptionMap.uninject();
+ InjectorPermissionMap.uninject();
+ InjectorDefaultsMap.uninject();
+
+ getLog().info("Closing storage...");
+ this.storage.shutdown();
+
+ if (this.fileWatcher != null) {
+ this.fileWatcher.close();
+ }
+
+ if (this.messagingService != null) {
+ getLog().info("Closing messaging service...");
+ this.messagingService.close();
+ }
+
+ ApiRegistrationUtil.unregisterProvider();
+ getServer().getServiceManager().cancel(this);
+
+ getLog().info("Shutting down internal scheduler...");
+ this.scheduler.shutdown();
+
+ // Nukkit will do this again when #onDisable completes, but we do it early to prevent NPEs elsewhere.
+ getServer().getScheduler().cancelTask(this);
+ HandlerList.unregisterAll(this);
+ getLog().info("Goodbye!");
+ }
+
+ public void refreshAutoOp(User user, Player player) {
+ if (user == null) {
+ return;
+ }
+
+ if (getConfiguration().get(ConfigKeys.AUTO_OP)) {
+ Map backing = user.getCachedData().getPermissionData(this.contextManager.getApplicableContexts(player)).getImmutableBacking();
+ boolean op = Optional.ofNullable(backing.get("luckperms.autoop")).orElse(false);
+ player.setOp(op);
+ }
+ }
+
+ private File resolveConfig(String file) {
+ File configFile = new File(getDataFolder(), file);
+
+ if (!configFile.exists()) {
+ getDataFolder().mkdirs();
+ saveResource("config.yml", false);
+ }
+
+ return configFile;
+ }
+
+ public LPSubscriptionMap getSubscriptionMap() {
+ return this.subscriptionMap;
+ }
+
+ public void setSubscriptionMap(LPSubscriptionMap subscriptionMap) {
+ this.subscriptionMap = subscriptionMap;
+ }
+
+ public LPPermissionMap getPermissionMap() {
+ return this.permissionMap;
+ }
+
+ public void setPermissionMap(LPPermissionMap permissionMap) {
+ this.permissionMap = permissionMap;
+ }
+
+ public LPDefaultsMap getDefaultPermissionMap() {
+ return this.defaultPermissionMap;
+ }
+
+ public void setDefaultPermissionMap(LPDefaultsMap defaultPermissionMap) {
+ this.defaultPermissionMap = defaultPermissionMap;
+ }
+
+ @Override
+ public Optional getMessagingService() {
+ return Optional.ofNullable(this.messagingService);
+ }
+
+ @Override
+ public void setMessagingService(InternalMessagingService messagingService) {
+ if (this.messagingService == null) {
+ this.messagingService = messagingService;
+ }
+ }
+
+ @Override
+ public Optional getFileWatcher() {
+ return Optional.ofNullable(this.fileWatcher);
+ }
+
+ @Override
+ public String getVersion() {
+ return getDescription().getVersion();
+ }
+
+ @Override
+ public PlatformType getServerType() {
+ return PlatformType.NUKKIT;
+ }
+
+ @Override
+ public String getServerBrand() {
+ return getServer().getName();
+ }
+
+ @Override
+ public String getServerVersion() {
+ return getServer().getVersion() + " - " + getServer().getNukkitVersion();
+ }
+
+ @Override
+ public String getServerName() {
+ return getServer().getServerUniqueId().toString();
+ }
+
+ @Override
+ public File getDataDirectory() {
+ return super.getDataFolder();
+ }
+
+ @Override
+ public InputStream getResourceStream(String path) {
+ return getResource(path);
+ }
+
+ @Override
+ public Player getPlayer(User user) {
+ return getServer().getOnlinePlayers().get(user.getUuid());
+ }
+
+ @Override
+ public Optional lookupUuid(String username) {
+ return Optional.empty();
+ }
+
+ @Nullable
+ @Override
+ public Contexts getContextForUser(User user) {
+ Player player = getPlayer(user);
+ if (player == null) {
+ return null;
+ }
+ return this.contextManager.getApplicableContexts(player);
+ }
+
+ @Override
+ public int getPlayerCount() {
+ return getServer().getOnlinePlayers().size();
+ }
+
+ @Override
+ public Stream getPlayerList() {
+ return getServer().getOnlinePlayers().values().stream().map(Player::getName);
+ }
+
+ @Override
+ public Stream getOnlinePlayers() {
+ return getServer().getOnlinePlayers().values().stream().map(Player::getUniqueId);
+ }
+
+ @Override
+ public boolean isPlayerOnline(UUID external) {
+ Player player = getServer().getOnlinePlayers().get(external);
+ return player != null && player.isOnline();
+ }
+
+ @Override
+ public Stream getOnlineSenders() {
+ return Stream.concat(
+ Stream.of(getConsoleSender()),
+ getServer().getOnlinePlayers().values().stream().map(p -> getSenderFactory().wrap(p))
+ );
+ }
+
+ @Override
+ public Sender getConsoleSender() {
+ return getSenderFactory().wrap(getServer().getConsoleSender());
+ }
+
+ @Override
+ public long getStartTime() {
+ return this.startTime;
+ }
+
+ @Override
+ public NukkitSchedulerAdapter getScheduler() {
+ return this.scheduler;
+ }
+
+ @Override
+ public NukkitCommandExecutor getCommandManager() {
+ return this.commandManager;
+ }
+
+ @Override
+ public LuckPermsConfiguration getConfiguration() {
+ return this.configuration;
+ }
+
+ @Override
+ public StandardUserManager getUserManager() {
+ return this.userManager;
+ }
+
+ @Override
+ public StandardGroupManager getGroupManager() {
+ return this.groupManager;
+ }
+
+ @Override
+ public StandardTrackManager getTrackManager() {
+ return this.trackManager;
+ }
+
+ @Override
+ public Storage getStorage() {
+ return this.storage;
+ }
+
+ @Override
+ public LuckPermsApiProvider getApiProvider() {
+ return this.apiProvider;
+ }
+
+ @Override
+ public EventFactory getEventFactory() {
+ return this.eventFactory;
+ }
+
+ @Override
+ public Logger getLog() {
+ return this.log;
+ }
+
+ @Override
+ public LocaleManager getLocaleManager() {
+ return this.localeManager;
+ }
+
+ @Override
+ public PluginClassLoader getPluginClassLoader() {
+ return this.pluginClassLoader;
+ }
+
+ @Override
+ public DependencyManager getDependencyManager() {
+ return this.dependencyManager;
+ }
+
+ @Override
+ public CachedStateManager getCachedStateManager() {
+ return this.cachedStateManager;
+ }
+
+ @Override
+ public ContextManager getContextManager() {
+ return this.contextManager;
+ }
+
+ @Override
+ public InheritanceHandler getInheritanceHandler() {
+ return this.inheritanceHandler;
+ }
+
+ @Override
+ public CalculatorFactory getCalculatorFactory() {
+ return this.calculatorFactory;
+ }
+
+ @Override
+ public BufferedRequest getUpdateTaskBuffer() {
+ return this.updateTaskBuffer;
+ }
+
+ @Override
+ public VerboseHandler getVerboseHandler() {
+ return this.verboseHandler;
+ }
+
+ public NukkitSenderFactory getSenderFactory() {
+ return this.senderFactory;
+ }
+
+ @Override
+ public PermissionVault getPermissionVault() {
+ return this.permissionVault;
+ }
+
+ @Override
+ public LogDispatcher getLogDispatcher() {
+ return this.logDispatcher;
+ }
+
+ @Override
+ public Set getUniqueConnections() {
+ return this.uniqueConnections;
+ }
+}
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitCommandExecutor.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitCommandExecutor.java
new file mode 100644
index 000000000..f592c9fe2
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitCommandExecutor.java
@@ -0,0 +1,59 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+
+import me.lucko.luckperms.common.commands.CommandManager;
+import me.lucko.luckperms.common.commands.sender.Sender;
+
+import cn.nukkit.command.Command;
+import cn.nukkit.command.CommandExecutor;
+import cn.nukkit.command.CommandSender;
+
+import java.util.List;
+
+public class NukkitCommandExecutor extends CommandManager implements CommandExecutor {
+ private static final Splitter ARGUMENT_SPLITTER = Splitter.on(COMMAND_SEPARATOR_PATTERN).omitEmptyStrings();
+ private static final Joiner ARGUMENT_JOINER = Joiner.on(' ');
+
+ private final LPNukkitPlugin plugin;
+
+ NukkitCommandExecutor(LPNukkitPlugin plugin) {
+ super(plugin);
+ this.plugin = plugin;
+ }
+
+ @Override
+ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+ Sender lpSender = this.plugin.getSenderFactory().wrap(sender);
+ List arguments = stripQuotes(ARGUMENT_SPLITTER.splitToList(ARGUMENT_JOINER.join(args)));
+
+ onCommand(lpSender, label, arguments);
+ return true;
+ }
+}
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitConfigAdapter.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitConfigAdapter.java
new file mode 100644
index 000000000..1b815860d
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitConfigAdapter.java
@@ -0,0 +1,109 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit;
+
+import me.lucko.luckperms.common.config.adapter.AbstractConfigurationAdapter;
+import me.lucko.luckperms.common.config.adapter.ConfigurationAdapter;
+import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
+
+import cn.nukkit.utils.Config;
+import cn.nukkit.utils.ConfigSection;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class NukkitConfigAdapter extends AbstractConfigurationAdapter implements ConfigurationAdapter {
+
+ private final File file;
+ private Config configuration;
+
+ public NukkitConfigAdapter(LuckPermsPlugin plugin, File file) {
+ super(plugin);
+ this.file = file;
+ reload();
+ }
+
+ @Override
+ public void reload() {
+ this.configuration = new Config(this.file, Config.YAML);
+ }
+
+ @Override
+ public boolean contains(String path) {
+ return this.configuration.exists(path);
+ }
+
+ @Override
+ public String getString(String path, String def) {
+ return this.configuration.getString(path, def);
+ }
+
+ @Override
+ public int getInt(String path, int def) {
+ return this.configuration.getInt(path, def);
+ }
+
+ @Override
+ public boolean getBoolean(String path, boolean def) {
+ return this.configuration.getBoolean(path, def);
+ }
+
+ @Override
+ public List getList(String path, List def) {
+ List ret = this.configuration.getStringList(path);
+ return ret == null ? def : ret;
+ }
+
+ @Override
+ public List getObjectList(String path, List def) {
+ ConfigSection section = this.configuration.getSection(path);
+ if (section == null) {
+ return def;
+ }
+
+ Set keys = section.getKeys(false);
+ return keys == null ? def : new ArrayList<>(keys);
+ }
+
+ @Override
+ public Map getMap(String path, Map def) {
+ Map map = new HashMap<>();
+ ConfigSection section = this.configuration.getSection(path);
+ if (section == null) {
+ return def;
+ }
+
+ for (String key : section.getKeys(false)) {
+ map.put(key, section.getString(key));
+ }
+
+ return map;
+ }
+}
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitSchedulerAdapter.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitSchedulerAdapter.java
new file mode 100644
index 000000000..0d852fbf0
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitSchedulerAdapter.java
@@ -0,0 +1,186 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+
+import me.lucko.luckperms.common.plugin.SchedulerAdapter;
+import me.lucko.luckperms.common.plugin.SchedulerTask;
+import me.lucko.luckperms.common.utils.SafeIteration;
+
+import cn.nukkit.scheduler.ServerScheduler;
+import cn.nukkit.scheduler.TaskHandler;
+
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.Nonnull;
+
+public class NukkitSchedulerAdapter implements SchedulerAdapter {
+ private final LPNukkitPlugin plugin;
+
+ private final ExecutorService asyncFallback;
+ private final Executor asyncNukkit;
+ private final Executor sync;
+ private final Executor async;
+
+ private boolean useFallback = true;
+
+ private final Set tasks = ConcurrentHashMap.newKeySet();
+
+ public NukkitSchedulerAdapter(LPNukkitPlugin plugin) {
+ this.plugin = plugin;
+
+ this.sync = new SyncExecutor();
+ this.asyncFallback = new FallbackAsyncExecutor();
+ this.asyncNukkit = new NukkitAsyncExecutor();
+ this.async = new AsyncExecutor();
+ }
+
+ private ServerScheduler scheduler() {
+ return this.plugin.getServer().getScheduler();
+ }
+
+ @Override
+ public void doAsync(Runnable runnable) {
+ async().execute(runnable);
+ }
+
+ @Override
+ public void doSync(Runnable runnable) {
+ sync().execute(runnable);
+ }
+
+ @Override
+ public SchedulerTask asyncRepeating(Runnable runnable, long intervalTicks) {
+ SchedulerTask task = new NukkitSchedulerTask(scheduler().scheduleDelayedRepeatingTask(this.plugin, runnable, (int) intervalTicks, (int) intervalTicks, true));
+ this.tasks.add(task);
+ return task;
+ }
+
+ @Override
+ public SchedulerTask syncRepeating(Runnable runnable, long intervalTicks) {
+ SchedulerTask task = new NukkitSchedulerTask(scheduler().scheduleDelayedRepeatingTask(this.plugin, runnable, (int) intervalTicks, (int) intervalTicks, false));
+ this.tasks.add(task);
+ return task;
+ }
+
+ @Override
+ public SchedulerTask asyncLater(Runnable runnable, long delayTicks) {
+ return new NukkitSchedulerTask(scheduler().scheduleDelayedTask(this.plugin, runnable, (int) delayTicks, true));
+ }
+
+ @Override
+ public SchedulerTask syncLater(Runnable runnable, long delayTicks) {
+ return new NukkitSchedulerTask(scheduler().scheduleDelayedTask(this.plugin, runnable, (int) delayTicks, false));
+ }
+
+ @Override
+ public void shutdown() {
+ SafeIteration.iterate(this.tasks, SchedulerTask::cancel);
+
+ // wait for executor
+ this.asyncFallback.shutdown();
+ try {
+ this.asyncFallback.awaitTermination(30, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ public ExecutorService asyncFallback() {
+ return this.asyncFallback;
+ }
+
+ public Executor asyncNukkit() {
+ return this.asyncNukkit;
+ }
+
+ @Override
+ public Executor sync() {
+ return this.sync;
+ }
+
+ @Override
+ public Executor async() {
+ return this.async;
+ }
+
+ public void setUseFallback(boolean useFallback) {
+ this.useFallback = useFallback;
+ }
+
+ private final class SyncExecutor implements Executor {
+ @Override
+ public void execute(@Nonnull Runnable runnable) {
+ NukkitSchedulerAdapter.this.plugin.getServer().getScheduler().scheduleTask(NukkitSchedulerAdapter.this.plugin, runnable, false);
+ }
+ }
+
+ private final class AsyncExecutor implements Executor {
+ @Override
+ public void execute(@Nonnull Runnable runnable) {
+ if (NukkitSchedulerAdapter.this.useFallback || !NukkitSchedulerAdapter.this.plugin.isEnabled()) {
+ NukkitSchedulerAdapter.this.asyncFallback.execute(runnable);
+ } else {
+ NukkitSchedulerAdapter.this.asyncNukkit.execute(runnable);
+ }
+ }
+ }
+
+ private final class NukkitAsyncExecutor implements Executor {
+ @Override
+ public void execute(@Nonnull Runnable runnable) {
+ NukkitSchedulerAdapter.this.plugin.getServer().getScheduler().scheduleTask(NukkitSchedulerAdapter.this.plugin, runnable, true);
+ }
+ }
+
+ private static final class FallbackAsyncExecutor extends ThreadPoolExecutor {
+ private FallbackAsyncExecutor() {
+ super(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(), new ThreadFactoryBuilder().setNameFormat("luckperms-fallback-%d").build());
+ }
+ }
+
+ private static final class NukkitSchedulerTask implements SchedulerTask {
+ private final TaskHandler task;
+
+ private NukkitSchedulerTask(TaskHandler task) {
+ this.task = task;
+ }
+
+ @Override
+ public void cancel() {
+ this.task.cancel();
+ }
+ }
+
+}
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitSenderFactory.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitSenderFactory.java
new file mode 100644
index 000000000..d1af4eb7c
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitSenderFactory.java
@@ -0,0 +1,109 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit;
+
+import me.lucko.luckperms.api.Tristate;
+import me.lucko.luckperms.common.commands.CommandManager;
+import me.lucko.luckperms.common.commands.sender.SenderFactory;
+import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
+import me.lucko.luckperms.common.utils.TextUtils;
+
+import net.kyori.text.Component;
+
+import cn.nukkit.Player;
+import cn.nukkit.command.CommandSender;
+import cn.nukkit.command.ConsoleCommandSender;
+
+import java.util.UUID;
+
+public class NukkitSenderFactory extends SenderFactory {
+ public NukkitSenderFactory(LuckPermsPlugin plugin) {
+ super(plugin);
+ }
+
+ @Override
+ protected String getName(CommandSender sender) {
+ if (sender instanceof Player) {
+ return sender.getName();
+ }
+ return CommandManager.CONSOLE_NAME;
+ }
+
+ @Override
+ protected UUID getUuid(CommandSender sender) {
+ if (sender instanceof Player) {
+ return ((Player) sender).getUniqueId();
+ }
+ return CommandManager.CONSOLE_UUID;
+ }
+
+ @Override
+ protected void sendMessage(CommandSender sender, String s) {
+ // we can safely send async for players and the console
+ if (sender instanceof Player || sender instanceof ConsoleCommandSender) {
+ sender.sendMessage(s);
+ return;
+ }
+
+ // otherwise, send the message sync
+ getPlugin().getScheduler().doSync(new SyncMessengerAgent(sender, s));
+ }
+
+ @Override
+ protected void sendMessage(CommandSender sender, Component message) {
+ // Fallback to legacy format
+ sendMessage(sender, TextUtils.toLegacy(message));
+ }
+
+ @Override
+ protected Tristate getPermissionValue(CommandSender sender, String node) {
+ boolean isSet = sender.isPermissionSet(node);
+ boolean val = sender.hasPermission(node);
+
+ return !isSet ? val ? Tristate.TRUE : Tristate.UNDEFINED : Tristate.fromBoolean(val);
+ }
+
+ @Override
+ protected boolean hasPermission(CommandSender sender, String node) {
+ return sender.hasPermission(node);
+ }
+
+ private static final class SyncMessengerAgent implements Runnable {
+ private final CommandSender sender;
+ private final String message;
+
+ private SyncMessengerAgent(CommandSender sender, String message) {
+ this.sender = sender;
+ this.message = message;
+ }
+
+ @Override
+ public void run() {
+ this.sender.sendMessage(this.message);
+ }
+ }
+
+}
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/calculators/NukkitCalculatorFactory.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/calculators/NukkitCalculatorFactory.java
new file mode 100644
index 000000000..e53dddddc
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/calculators/NukkitCalculatorFactory.java
@@ -0,0 +1,75 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit.calculators;
+
+import com.google.common.collect.ImmutableList;
+
+import me.lucko.luckperms.api.Contexts;
+import me.lucko.luckperms.common.calculators.AbstractCalculatorFactory;
+import me.lucko.luckperms.common.calculators.PermissionCalculator;
+import me.lucko.luckperms.common.calculators.PermissionCalculatorMetadata;
+import me.lucko.luckperms.common.config.ConfigKeys;
+import me.lucko.luckperms.common.processors.MapProcessor;
+import me.lucko.luckperms.common.processors.PermissionProcessor;
+import me.lucko.luckperms.common.processors.RegexProcessor;
+import me.lucko.luckperms.common.processors.WildcardProcessor;
+import me.lucko.luckperms.common.references.HolderType;
+import me.lucko.luckperms.nukkit.LPNukkitPlugin;
+import me.lucko.luckperms.nukkit.processors.ChildProcessor;
+import me.lucko.luckperms.nukkit.processors.DefaultsProcessor;
+
+public class NukkitCalculatorFactory extends AbstractCalculatorFactory {
+ private final LPNukkitPlugin plugin;
+
+ public NukkitCalculatorFactory(LPNukkitPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public PermissionCalculator build(Contexts contexts, PermissionCalculatorMetadata metadata) {
+ ImmutableList.Builder processors = ImmutableList.builder();
+
+ processors.add(new MapProcessor());
+
+ if (this.plugin.getConfiguration().get(ConfigKeys.APPLY_NUKKIT_CHILD_PERMISSIONS)) {
+ processors.add(new ChildProcessor(this.plugin));
+ }
+
+ if (this.plugin.getConfiguration().get(ConfigKeys.APPLYING_REGEX)) {
+ processors.add(new RegexProcessor());
+ }
+
+ if (this.plugin.getConfiguration().get(ConfigKeys.APPLYING_WILDCARDS)) {
+ processors.add(new WildcardProcessor());
+ }
+
+ if (this.plugin.getConfiguration().get(ConfigKeys.APPLY_NUKKIT_DEFAULT_PERMISSIONS) && metadata.getHolderType() == HolderType.USER) {
+ processors.add(new DefaultsProcessor(this.plugin, contexts.isOp()));
+ }
+
+ return registerCalculator(new PermissionCalculator(this.plugin, metadata, processors.build()));
+ }
+}
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/contexts/NukkitContextManager.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/contexts/NukkitContextManager.java
new file mode 100644
index 000000000..2e07d1163
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/contexts/NukkitContextManager.java
@@ -0,0 +1,53 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit.contexts;
+
+import me.lucko.luckperms.api.Contexts;
+import me.lucko.luckperms.api.context.ImmutableContextSet;
+import me.lucko.luckperms.common.config.ConfigKeys;
+import me.lucko.luckperms.common.contexts.AbstractContextManager;
+import me.lucko.luckperms.nukkit.LPNukkitPlugin;
+
+import cn.nukkit.Player;
+
+public class NukkitContextManager extends AbstractContextManager {
+ public NukkitContextManager(LPNukkitPlugin plugin) {
+ super(plugin, Player.class);
+ }
+
+ @Override
+ public Contexts formContexts(Player subject, ImmutableContextSet contextSet) {
+ return new Contexts(
+ contextSet,
+ this.plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
+ this.plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
+ true,
+ this.plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
+ this.plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
+ subject.isOp()
+ );
+ }
+}
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/contexts/WorldCalculator.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/contexts/WorldCalculator.java
new file mode 100644
index 000000000..73b9d1519
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/contexts/WorldCalculator.java
@@ -0,0 +1,56 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit.contexts;
+
+import me.lucko.luckperms.api.Contexts;
+import me.lucko.luckperms.api.context.ContextCalculator;
+import me.lucko.luckperms.api.context.MutableContextSet;
+import me.lucko.luckperms.common.config.ConfigKeys;
+import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
+
+import cn.nukkit.Player;
+
+import javax.annotation.Nonnull;
+
+public class WorldCalculator implements ContextCalculator {
+ private final LuckPermsPlugin plugin;
+
+ public WorldCalculator(LuckPermsPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @Nonnull
+ @Override
+ public MutableContextSet giveApplicableContext(@Nonnull Player subject, @Nonnull MutableContextSet accumulator) {
+ String world = subject.getLevel().getName().toLowerCase();
+ while (!accumulator.has(Contexts.WORLD_KEY, world)) {
+ accumulator.add(Contexts.WORLD_KEY, world);
+ world = this.plugin.getConfiguration().get(ConfigKeys.WORLD_REWRITES).getOrDefault(world, world).toLowerCase();
+ }
+
+ return accumulator;
+ }
+}
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/listeners/NukkitConnectionListener.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/listeners/NukkitConnectionListener.java
new file mode 100644
index 000000000..0de1fcbf1
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/listeners/NukkitConnectionListener.java
@@ -0,0 +1,205 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit.listeners;
+
+import me.lucko.luckperms.common.config.ConfigKeys;
+import me.lucko.luckperms.common.locale.Message;
+import me.lucko.luckperms.common.model.User;
+import me.lucko.luckperms.common.utils.AbstractLoginListener;
+import me.lucko.luckperms.nukkit.LPNukkitPlugin;
+import me.lucko.luckperms.nukkit.model.permissible.LPPermissible;
+import me.lucko.luckperms.nukkit.model.permissible.PermissibleInjector;
+
+import cn.nukkit.Player;
+import cn.nukkit.event.EventHandler;
+import cn.nukkit.event.EventPriority;
+import cn.nukkit.event.Listener;
+import cn.nukkit.event.player.PlayerAsyncPreLoginEvent;
+import cn.nukkit.event.player.PlayerLoginEvent;
+import cn.nukkit.event.player.PlayerQuitEvent;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
+public class NukkitConnectionListener extends AbstractLoginListener implements Listener {
+ private final LPNukkitPlugin plugin;
+
+ private final Set deniedAsyncLogin = Collections.synchronizedSet(new HashSet<>());
+ private final Set deniedLogin = Collections.synchronizedSet(new HashSet<>());
+
+ public NukkitConnectionListener(LPNukkitPlugin plugin) {
+ super(plugin);
+ this.plugin = plugin;
+ }
+
+ @EventHandler(priority = EventPriority.LOW)
+ public void onPlayerPreLogin(PlayerAsyncPreLoginEvent e) {
+ /* Called when the player first attempts a connection with the server.
+ Listening on LOW priority to allow plugins to modify username / UUID data here. (auth plugins) */
+
+ if (this.plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) {
+ this.plugin.getLog().info("Processing pre-login for " + e.getUuid() + " - " + e.getName());
+ }
+
+ this.plugin.getUniqueConnections().add(e.getUuid());
+
+ /* Actually process the login for the connection.
+ We do this here to delay the login until the data is ready.
+ If the login gets cancelled later on, then this will be cleaned up.
+
+ This includes:
+ - loading uuid data
+ - loading permissions
+ - creating a user instance in the UserManager for this connection.
+ - setting up cached data. */
+ try {
+ User user = loadUser(e.getUuid(), e.getName());
+ this.plugin.getEventFactory().handleUserLoginProcess(e.getUuid(), e.getName(), user);
+ } catch (Exception ex) {
+ this.plugin.getLog().severe("Exception occurred whilst loading data for " + e.getUuid() + " - " + e.getName());
+ ex.printStackTrace();
+
+ // deny the connection
+ this.deniedAsyncLogin.add(e.getUuid());
+ e.disAllow(Message.LOADING_ERROR.asString(this.plugin.getLocaleManager()));
+ }
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void onPlayerPreLoginMonitor(PlayerAsyncPreLoginEvent e) {
+ /* Listen to see if the event was cancelled after we initially handled the connection
+ If the connection was cancelled here, we need to do something to clean up the data that was loaded. */
+
+ // Check to see if this connection was denied at LOW.
+ if (this.deniedAsyncLogin.remove(e.getUuid())) {
+ // their data was never loaded at LOW priority, now check to see if they have been magically allowed since then.
+
+ // This is a problem, as they were denied at low priority, but are now being allowed.
+ if (e.getLoginResult() == PlayerAsyncPreLoginEvent.LoginResult.SUCCESS) {
+ this.plugin.getLog().severe("Player connection was re-allowed for " + e.getUuid());
+ e.disAllow("");
+ }
+ }
+ }
+
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void onPlayerLogin(PlayerLoginEvent e) {
+ /* Called when the player starts logging into the server.
+ At this point, the users data should be present and loaded. */
+
+ final Player player = e.getPlayer();
+
+ if (this.plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) {
+ this.plugin.getLog().info("Processing login for " + player.getUniqueId() + " - " + player.getName());
+ }
+
+ final User user = this.plugin.getUserManager().getIfLoaded(player.getUniqueId());
+
+ /* User instance is null for whatever reason. Could be that it was unloaded between asyncpre and now. */
+ if (user == null) {
+ this.deniedLogin.add(e.getPlayer().getUniqueId());
+
+ this.plugin.getLog().warn("User " + player.getUniqueId() + " - " + player.getName() + " doesn't have data pre-loaded. - denying login.");
+ e.setCancelled();
+ e.setKickMessage(Message.LOADING_ERROR.asString(this.plugin.getLocaleManager()));
+ return;
+ }
+
+ // User instance is there, now we can inject our custom Permissible into the player.
+ // Care should be taken at this stage to ensure that async tasks which manipulate nukkit data check that the player is still online.
+ try {
+ // Make a new permissible for the user
+ LPPermissible lpPermissible = new LPPermissible(player, user, this.plugin);
+
+ // Inject into the player
+ PermissibleInjector.inject(player, lpPermissible);
+
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+
+ this.plugin.refreshAutoOp(user, player);
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void onPlayerLoginMonitor(PlayerLoginEvent e) {
+ /* Listen to see if the event was cancelled after we initially handled the login
+ If the connection was cancelled here, we need to do something to clean up the data that was loaded. */
+
+ // Check to see if this connection was denied at LOW. Even if it was denied at LOW, their data will still be present.
+ boolean denied = false;
+ if (this.deniedLogin.remove(e.getPlayer().getUniqueId())) {
+ denied = true;
+
+ // This is a problem, as they were denied at low priority, but are now being allowed.
+ if (!e.isCancelled()) {
+ this.plugin.getLog().severe("Player connection was re-allowed for " + e.getPlayer().getUniqueId());
+ e.setCancelled();
+ }
+ }
+
+ // Login event was cancelled by another plugin since we first loaded their data
+ if (denied || e.isCancelled()) {
+ return;
+ }
+
+ // everything is going well. login was processed ok, this is just to refresh auto-op status.
+ this.plugin.refreshAutoOp(this.plugin.getUserManager().getIfLoaded(e.getPlayer().getUniqueId()), e.getPlayer());
+ }
+
+ // Wait until the last priority to unload, so plugins can still perform permission checks on this event
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void onPlayerQuit(PlayerQuitEvent e) {
+ final Player player = e.getPlayer();
+
+ // Remove the custom permissible
+ try {
+ PermissibleInjector.unInject(player, true);
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+
+ // Handle auto op
+ if (this.plugin.getConfiguration().get(ConfigKeys.AUTO_OP)) {
+ player.setOp(false);
+ }
+
+ // Register with the housekeeper, so the User's instance will stick
+ // around for a bit after they disconnect
+ this.plugin.getUserManager().getHouseKeeper().registerUsage(player.getUniqueId());
+
+ // force a clear of transient nodes
+ this.plugin.getScheduler().doAsync(() -> {
+ User user = this.plugin.getUserManager().getIfLoaded(player.getUniqueId());
+ if (user != null) {
+ user.clearTransientNodes();
+ }
+ });
+ }
+
+}
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/listeners/NukkitPlatformListener.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/listeners/NukkitPlatformListener.java
new file mode 100644
index 000000000..d15e49bd5
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/listeners/NukkitPlatformListener.java
@@ -0,0 +1,97 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit.listeners;
+
+import me.lucko.luckperms.common.config.ConfigKeys;
+import me.lucko.luckperms.common.locale.Message;
+import me.lucko.luckperms.nukkit.LPNukkitPlugin;
+
+import cn.nukkit.Player;
+import cn.nukkit.command.CommandSender;
+import cn.nukkit.event.Cancellable;
+import cn.nukkit.event.EventHandler;
+import cn.nukkit.event.EventPriority;
+import cn.nukkit.event.Listener;
+import cn.nukkit.event.entity.EntityLevelChangeEvent;
+import cn.nukkit.event.player.PlayerCommandPreprocessEvent;
+import cn.nukkit.event.server.RemoteServerCommandEvent;
+import cn.nukkit.event.server.ServerCommandEvent;
+
+public class NukkitPlatformListener implements Listener {
+ private final LPNukkitPlugin plugin;
+
+ public NukkitPlatformListener(LPNukkitPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @EventHandler
+ public void onPlayerCommand(PlayerCommandPreprocessEvent e) {
+ handleCommand(e.getPlayer(), e.getMessage().toLowerCase(), e);
+ }
+
+ @EventHandler
+ public void onServerCommand(ServerCommandEvent e) {
+ handleCommand(e.getSender(), e.getCommand().toLowerCase(), e);
+ }
+
+ @EventHandler
+ public void onRemoteServerCommand(RemoteServerCommandEvent e) {
+ handleCommand(e.getSender(), e.getCommand().toLowerCase(), e);
+ }
+
+ private void handleCommand(CommandSender sender, String s, Cancellable event) {
+ if (s.isEmpty()) {
+ return;
+ }
+
+ if (this.plugin.getConfiguration().get(ConfigKeys.OPS_ENABLED)) {
+ return;
+ }
+
+ if (s.charAt(0) == '/') {
+ s = s.substring(1);
+ }
+
+ if (s.startsWith("minecraft:")) {
+ s = s.substring("minecraft:".length());
+ }
+
+ if (s.equals("op") || s.startsWith("op ") || s.equals("deop") || s.startsWith("deop ")) {
+ event.setCancelled(true);
+ sender.sendMessage(Message.OP_DISABLED.asString(this.plugin.getLocaleManager()));
+ }
+ }
+
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void onWorldChange(EntityLevelChangeEvent e) {
+ if (e.getEntity() instanceof Player) {
+ Player player = (Player) e.getEntity();
+ this.plugin.getContextManager().invalidateCache(player);
+ this.plugin.refreshAutoOp(this.plugin.getUserManager().getIfLoaded(player.getUniqueId()), player);
+ }
+ }
+
+}
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/PermissionDefault.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/PermissionDefault.java
new file mode 100644
index 000000000..9cd38e257
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/PermissionDefault.java
@@ -0,0 +1,111 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit.model;
+
+import cn.nukkit.permission.Permission;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+/**
+ * Represents the possible default values for permissions
+ */
+public enum PermissionDefault {
+ TRUE("true") {
+ @Override
+ public boolean getValue(boolean op) {
+ return true;
+ }
+ },
+ FALSE("false") {
+ @Override
+ public boolean getValue(boolean op) {
+ return false;
+ }
+ },
+ OP("op", "isop", "operator", "isoperator", "admin", "isadmin") {
+ @Override
+ public boolean getValue(boolean op) {
+ return op;
+ }
+ },
+ NOT_OP("!op", "notop", "!operator", "notoperator", "!admin", "notadmin") {
+ @Override
+ public boolean getValue(boolean op) {
+ return !op;
+ }
+ };
+
+ private final String[] names;
+ private static final Map LOOKUP = new HashMap<>();
+
+ PermissionDefault(String... names) {
+ this.names = names;
+ }
+
+ /**
+ * Calculates the value of this PermissionDefault for the given operator
+ * value
+ *
+ * @param op If the target is op
+ * @return True if the default should be true, or false
+ */
+ public abstract boolean getValue(boolean op);
+
+ /**
+ * Looks up a PermissionDefault by name
+ *
+ * @param name Name of the default
+ * @return Specified value, or null if not found
+ */
+ @Nullable
+ public static PermissionDefault getByName(String name) {
+ return LOOKUP.get(name.toLowerCase().replaceAll("[^a-z!]", ""));
+ }
+
+ @Nullable
+ public static PermissionDefault fromPermission(@Nullable Permission permission) {
+ if (permission == null) {
+ return null;
+ }
+ return getByName(permission.getDefault());
+ }
+
+ @Override
+ public String toString() {
+ return this.names[0];
+ }
+
+ static {
+ for (PermissionDefault value : values()) {
+ for (String name : value.names) {
+ LOOKUP.put(name, value);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/dummy/DummyPermissibleBase.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/dummy/DummyPermissibleBase.java
new file mode 100644
index 000000000..15f7b54ed
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/dummy/DummyPermissibleBase.java
@@ -0,0 +1,88 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit.model.dummy;
+
+import cn.nukkit.permission.PermissibleBase;
+import cn.nukkit.permission.Permission;
+import cn.nukkit.permission.PermissionAttachment;
+import cn.nukkit.permission.PermissionAttachmentInfo;
+import cn.nukkit.plugin.Plugin;
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.Map;
+
+public class DummyPermissibleBase extends PermissibleBase {
+ private static final Field ATTACHMENTS_FIELD;
+ private static final Field PERMISSIONS_FIELD;
+
+ static {
+ try {
+ ATTACHMENTS_FIELD = PermissibleBase.class.getDeclaredField("attachments");
+ ATTACHMENTS_FIELD.setAccessible(true);
+
+ PERMISSIONS_FIELD = PermissibleBase.class.getDeclaredField("permissions");
+ PERMISSIONS_FIELD.setAccessible(true);
+ } catch (NoSuchFieldException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ public static void nullFields(PermissibleBase permissibleBase) {
+ try {
+ ATTACHMENTS_FIELD.set(permissibleBase, null);
+ } catch (Exception e) {
+ // ignore
+ }
+ try {
+ PERMISSIONS_FIELD.set(permissibleBase, null);
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+
+ public static final DummyPermissibleBase INSTANCE = new DummyPermissibleBase();
+
+ private DummyPermissibleBase() {
+ super(null);
+ nullFields(this);
+ }
+
+ @Override public boolean isOp() { return false; }
+ @Override public void setOp(boolean value) {}
+ @Override public boolean isPermissionSet(String name) { return false; }
+ @Override public boolean isPermissionSet(Permission perm) { return false; }
+ @Override public boolean hasPermission(String inName) { return false; }
+ @Override public boolean hasPermission(Permission perm) { return false; }
+ @Override public PermissionAttachment addAttachment(Plugin plugin) { return null; }
+ @Override public PermissionAttachment addAttachment(Plugin plugin, String name) { return null; }
+ @Override public PermissionAttachment addAttachment(Plugin plugin, String name, Boolean value) { return null; }
+ @Override public void removeAttachment(PermissionAttachment attachment) {}
+ @Override public void recalculatePermissions() {}
+ @Override public void clearPermissions() {}
+ @Override public Map getEffectivePermissions() { return Collections.emptyMap(); }
+
+}
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/dummy/DummyPlugin.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/dummy/DummyPlugin.java
new file mode 100644
index 000000000..e5e1fc97f
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/dummy/DummyPlugin.java
@@ -0,0 +1,78 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit.model.dummy;
+import cn.nukkit.Server;
+import cn.nukkit.command.Command;
+import cn.nukkit.command.CommandSender;
+import cn.nukkit.plugin.Plugin;
+import cn.nukkit.plugin.PluginDescription;
+import cn.nukkit.plugin.PluginLoader;
+import cn.nukkit.plugin.PluginLogger;
+import cn.nukkit.utils.Config;
+
+import java.io.File;
+import java.io.InputStream;
+
+/**
+ * Dummy plugin instance
+ */
+public class DummyPlugin implements Plugin {
+ public static final DummyPlugin INSTANCE = new DummyPlugin();
+
+ private DummyPlugin() {
+
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isDisabled() {
+ return false;
+ }
+
+ @Override public File getDataFolder() { return null; }
+ @Override public PluginDescription getDescription() { return null; }
+ @Override public Config getConfig() { return null; }
+ @Override public InputStream getResource(String s) { return null; }
+ @Override public boolean saveResource(String s) { return false; }
+ @Override public void saveConfig() {}
+ @Override public void saveDefaultConfig() {}
+ @Override public boolean saveResource(String s, boolean b) { return false; }
+ @Override public boolean saveResource(String s, String s1, boolean b) { return false; }
+ @Override public void reloadConfig() {}
+ @Override public PluginLoader getPluginLoader() { return null; }
+ @Override public Server getServer() { return null; }
+ @Override public void onDisable() {}
+ @Override public void onLoad() {}
+ @Override public void onEnable() {}
+ @Override public PluginLogger getLogger() { return null; }
+ @Override public String getName() { return null; }
+ @Override public boolean onCommand(CommandSender commandSender, Command command, String s, String[] strings) { return false; }
+
+}
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/LPPermissible.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/LPPermissible.java
new file mode 100644
index 000000000..cb9ad391a
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/LPPermissible.java
@@ -0,0 +1,279 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit.model.permissible;
+
+import me.lucko.luckperms.api.Contexts;
+import me.lucko.luckperms.api.Tristate;
+import me.lucko.luckperms.common.config.ConfigKeys;
+import me.lucko.luckperms.common.model.User;
+import me.lucko.luckperms.common.verbose.CheckOrigin;
+import me.lucko.luckperms.nukkit.LPNukkitPlugin;
+import me.lucko.luckperms.nukkit.model.PermissionDefault;
+
+import cn.nukkit.Player;
+import cn.nukkit.permission.PermissibleBase;
+import cn.nukkit.permission.Permission;
+import cn.nukkit.permission.PermissionAttachment;
+import cn.nukkit.permission.PermissionAttachmentInfo;
+import cn.nukkit.plugin.Plugin;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * PermissibleBase for LuckPerms.
+ *
+ * This class overrides all methods defined in PermissibleBase, and provides custom handling
+ * from LuckPerms.
+ *
+ * This means that all permission checks made for a player are handled directly by the plugin.
+ * Method behaviour is retained, but alternate implementation is used.
+ *
+ * "Hot" method calls, (namely #hasPermission) are significantly faster than the base implementation.
+ *
+ * This class is **thread safe**. This means that when LuckPerms is installed on the server,
+ * is is safe to call Player#hasPermission asynchronously.
+ */
+public class LPPermissible extends PermissibleBase {
+
+ // the LuckPerms user this permissible references.
+ private final User user;
+
+ // the player this permissible is injected into.
+ private final Player player;
+
+ // the luckperms plugin instance
+ private final LPNukkitPlugin plugin;
+
+ // the players previous permissible. (the one they had before this one was injected)
+ private PermissibleBase oldPermissible = null;
+
+ // if the permissible is currently active.
+ private final AtomicBoolean active = new AtomicBoolean(false);
+
+ // the attachments hooked onto the permissible.
+ // this collection is only modified by the attachments themselves
+ final Set attachments = ConcurrentHashMap.newKeySet();
+
+ public LPPermissible(Player player, User user, LPNukkitPlugin plugin) {
+ super(player);
+ this.user = Objects.requireNonNull(user, "user");
+ this.player = Objects.requireNonNull(player, "player");
+ this.plugin = Objects.requireNonNull(plugin, "plugin");
+ }
+
+ @Override
+ public boolean isPermissionSet(String permission) {
+ if (permission == null) {
+ throw new NullPointerException("permission");
+ }
+
+ Tristate ts = this.user.getCachedData().getPermissionData(calculateContexts()).getPermissionValue(permission, CheckOrigin.PLATFORM_LOOKUP_CHECK);
+ return ts != Tristate.UNDEFINED || PermissionDefault.OP.getValue(isOp());
+ }
+
+ @Override
+ public boolean isPermissionSet(Permission permission) {
+ if (permission == null) {
+ throw new NullPointerException("permission");
+ }
+
+ Tristate ts = this.user.getCachedData().getPermissionData(calculateContexts()).getPermissionValue(permission.getName(), CheckOrigin.PLATFORM_LOOKUP_CHECK);
+ if (ts != Tristate.UNDEFINED) {
+ return true;
+ }
+
+ if (!this.plugin.getConfiguration().get(ConfigKeys.APPLY_NUKKIT_DEFAULT_PERMISSIONS)) {
+ return PermissionDefault.OP.getValue(isOp());
+ } else {
+ PermissionDefault def = PermissionDefault.fromPermission(permission);
+ return def != null && def.getValue(isOp());
+ }
+ }
+
+ @Override
+ public boolean hasPermission(String permission) {
+ if (permission == null) {
+ throw new NullPointerException("permission");
+ }
+
+ Tristate ts = this.user.getCachedData().getPermissionData(calculateContexts()).getPermissionValue(permission, CheckOrigin.PLATFORM_PERMISSION_CHECK);
+ return ts != Tristate.UNDEFINED ? ts.asBoolean() : PermissionDefault.OP.getValue(isOp());
+ }
+
+ @Override
+ public boolean hasPermission(Permission permission) {
+ if (permission == null) {
+ throw new NullPointerException("permission");
+ }
+
+ Tristate ts = this.user.getCachedData().getPermissionData(calculateContexts()).getPermissionValue(permission.getName(), CheckOrigin.PLATFORM_PERMISSION_CHECK);
+ if (ts != Tristate.UNDEFINED) {
+ return ts.asBoolean();
+ }
+
+ if (!this.plugin.getConfiguration().get(ConfigKeys.APPLY_NUKKIT_DEFAULT_PERMISSIONS)) {
+ return PermissionDefault.OP.getValue(isOp());
+ } else {
+ PermissionDefault def = PermissionDefault.fromPermission(permission);
+ return def != null && def.getValue(isOp());
+ }
+ }
+
+ /**
+ * Adds attachments to this permissible.
+ *
+ * @param attachments the attachments to add
+ */
+ public void convertAndAddAttachments(Collection attachments) {
+ for (PermissionAttachment attachment : attachments) {
+ new LPPermissionAttachment(this, attachment).hook();
+ }
+ }
+
+ /**
+ * Obtains a {@link Contexts} instance for the player.
+ * Values are determined using the plugins ContextManager.
+ *
+ * @return the calculated contexts for the player.
+ */
+ private Contexts calculateContexts() {
+ return this.plugin.getContextManager().getApplicableContexts(this.player);
+ }
+
+ @Override
+ public void setOp(boolean value) {
+ this.player.setOp(value);
+ }
+
+ @Override
+ public Map getEffectivePermissions() {
+ Set> permissions = this.user.getCachedData().getPermissionData(calculateContexts()).getImmutableBacking().entrySet();
+ Map ret = new HashMap<>(permissions.size());
+
+ for (Map.Entry entry : permissions) {
+ ret.put(entry.getKey(), new PermissionAttachmentInfo(this.player, entry.getKey(), null, entry.getValue()));
+ }
+
+ return ret;
+ }
+
+ @Override
+ public LPPermissionAttachment addAttachment(Plugin plugin) {
+ if (plugin == null) {
+ throw new NullPointerException("plugin");
+ }
+
+ LPPermissionAttachment ret = new LPPermissionAttachment(this, plugin);
+ ret.hook();
+ return ret;
+ }
+
+ @Override
+ public PermissionAttachment addAttachment(Plugin plugin, String permission) {
+ if (plugin == null) {
+ throw new NullPointerException("plugin");
+ }
+ if (permission == null) {
+ throw new NullPointerException("permission");
+ }
+
+ PermissionAttachment ret = addAttachment(plugin);
+ ret.setPermission(permission, true);
+ return ret;
+ }
+
+ @Override
+ public PermissionAttachment addAttachment(Plugin plugin, String permission, Boolean value) {
+ if (plugin == null) {
+ throw new NullPointerException("plugin");
+ }
+ if (permission == null) {
+ throw new NullPointerException("permission");
+ }
+
+ PermissionAttachment ret = addAttachment(plugin);
+ ret.setPermission(permission, value);
+ return ret;
+ }
+
+ @Override
+ public void removeAttachment(PermissionAttachment attachment) {
+ if (attachment == null) {
+ throw new NullPointerException("attachment");
+ }
+
+ if (!(attachment instanceof LPPermissionAttachment)) {
+ throw new IllegalArgumentException("Given attachment is not a LPPermissionAttachment.");
+ }
+
+ LPPermissionAttachment a = ((LPPermissionAttachment) attachment);
+ if (a.getPermissible() != this) {
+ throw new IllegalArgumentException("Attachment does not belong to this permissible.");
+ }
+
+ a.remove();
+ }
+
+ @Override
+ public void recalculatePermissions() {
+ // do nothing
+ }
+
+ @Override
+ public void clearPermissions() {
+ this.attachments.forEach(LPPermissionAttachment::remove);
+ }
+
+ public User getUser() {
+ return this.user;
+ }
+
+ public Player getPlayer() {
+ return this.player;
+ }
+
+ public LPNukkitPlugin getPlugin() {
+ return this.plugin;
+ }
+
+ public PermissibleBase getOldPermissible() {
+ return this.oldPermissible;
+ }
+
+ public AtomicBoolean getActive() {
+ return this.active;
+ }
+
+ public void setOldPermissible(PermissibleBase oldPermissible) {
+ this.oldPermissible = oldPermissible;
+ }
+}
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/LPPermissionAttachment.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/LPPermissionAttachment.java
new file mode 100644
index 000000000..fa111ddeb
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/LPPermissionAttachment.java
@@ -0,0 +1,432 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit.model.permissible;
+
+import com.google.common.base.Preconditions;
+
+import me.lucko.luckperms.api.Node;
+import me.lucko.luckperms.common.config.ConfigKeys;
+import me.lucko.luckperms.common.model.User;
+import me.lucko.luckperms.common.node.ImmutableTransientNode;
+import me.lucko.luckperms.common.node.NodeFactory;
+import me.lucko.luckperms.nukkit.model.dummy.DummyPlugin;
+
+import cn.nukkit.permission.Permission;
+import cn.nukkit.permission.PermissionAttachment;
+import cn.nukkit.permission.PermissionRemovedExecutor;
+import cn.nukkit.plugin.Plugin;
+
+import java.lang.reflect.Field;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * PermissionAttachment for LuckPerms.
+ *
+ * Applies all permissions directly to the backing user instance via transient nodes.
+ */
+public class LPPermissionAttachment extends PermissionAttachment {
+
+ /**
+ * The field in PermissionAttachment where the attachments applied permissions
+ * are *usually* held.
+ */
+ private static final Field PERMISSION_ATTACHMENT_PERMISSIONS_FIELD;
+
+ static {
+ try {
+ PERMISSION_ATTACHMENT_PERMISSIONS_FIELD = PermissionAttachment.class.getDeclaredField("permissions");
+ PERMISSION_ATTACHMENT_PERMISSIONS_FIELD.setAccessible(true);
+ } catch (NoSuchFieldException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ /**
+ * The parent LPPermissible
+ */
+ private final LPPermissible permissible;
+
+ /**
+ * The plugin which "owns" this attachment, may be null
+ */
+ private final Plugin owner;
+
+ /**
+ * The permissions being applied by this attachment
+ */
+ private final Map perms = Collections.synchronizedMap(new HashMap<>());
+
+ /**
+ * If the attachment has been applied to the user
+ */
+ private boolean hooked = false;
+
+ /**
+ * Callback to run when the attachment is removed
+ */
+ private PermissionRemovedExecutor removalCallback = null;
+
+ public LPPermissionAttachment(LPPermissible permissible, Plugin owner) {
+ super(DummyPlugin.INSTANCE, null);
+ this.permissible = permissible;
+ this.owner = owner;
+
+ injectFakeMap();
+ }
+
+ public LPPermissionAttachment(LPPermissible permissible, PermissionAttachment nukkit) {
+ super(DummyPlugin.INSTANCE, null);
+ this.permissible = permissible;
+ this.owner = null;
+
+ // copy
+ this.perms.putAll(nukkit.getPermissions());
+
+ injectFakeMap();
+ }
+
+ /**
+ * Injects a fake 'permissions' map into the superclass, for (clever/dumb??) plugins
+ * which attempt to modify attachment permissions using reflection to get around the slow nukkit
+ * behaviour in the base PermissionAttachment implementation.
+ *
+ * The fake map proxies calls back to the methods on this attachment
+ */
+ private void injectFakeMap() {
+ // inner class - this proxies calls back to us
+ FakeBackingMap fakeMap = new FakeBackingMap();
+
+ try {
+ // the field we need to modify is in the superclass - it has private
+ // and final modifiers so we have to use reflection to modify it.
+ PERMISSION_ATTACHMENT_PERMISSIONS_FIELD.set(this, fakeMap);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public LPPermissible getPermissible() {
+ return this.permissible;
+ }
+
+ @Override
+ public PermissionRemovedExecutor getRemovalCallback() {
+ return this.removalCallback;
+ }
+
+ @Override
+ public void setRemovalCallback(PermissionRemovedExecutor removalCallback) {
+ this.removalCallback = removalCallback;
+ }
+
+ /**
+ * Hooks this attachment with the parent {@link User} instance.
+ */
+ public void hook() {
+ this.hooked = true;
+ this.permissible.attachments.add(this);
+ for (Map.Entry entry : this.perms.entrySet()) {
+ if (entry.getKey() == null || entry.getKey().isEmpty()) {
+ continue;
+ }
+ setPermissionInternal(entry.getKey(), entry.getValue());
+ }
+ }
+
+ private void setPermissionInternal(String name, boolean value) {
+ if (!this.permissible.getPlugin().getConfiguration().get(ConfigKeys.APPLY_NUKKIT_ATTACHMENT_PERMISSIONS)) {
+ return;
+ }
+
+ // construct a node for the permission being set
+ // we use the servers static context to *try* to ensure that the node will apply
+ Node node = NodeFactory.builder(name)
+ .setValue(value)
+ .withExtraContext(this.permissible.getPlugin().getContextManager().getStaticContext())
+ .build();
+
+ // convert the constructed node to a transient node instance to refer back to this attachment
+ ImmutableTransientNode transientNode = ImmutableTransientNode.of(node, this);
+
+ // set the transient node
+ User user = this.permissible.getUser();
+ if (user.setTransientPermission(transientNode).asBoolean()) {
+ user.reloadCachedData();
+ }
+ }
+
+ private void unsetPermissionInternal(String name) {
+ if (!this.permissible.getPlugin().getConfiguration().get(ConfigKeys.APPLY_NUKKIT_ATTACHMENT_PERMISSIONS)) {
+ return;
+ }
+
+ // remove transient permissions from the holder which were added by this attachment & equal the permission
+ User user = this.permissible.getUser();
+ if (user.removeIfTransient(n -> n instanceof ImmutableTransientNode && ((ImmutableTransientNode) n).getOwner() == this && n.getPermission().equals(name))) {
+ user.reloadCachedData();
+ }
+ }
+
+ private void clearInternal() {
+ // remove all transient permissions added by this attachment
+ User user = this.permissible.getUser();
+ if (user.removeIfTransient(n -> n instanceof ImmutableTransientNode && ((ImmutableTransientNode) n).getOwner() == this)) {
+ user.reloadCachedData();
+ }
+ }
+
+ @Override
+ public void remove() {
+ if (!this.hooked) {
+ return;
+ }
+
+ // clear the internal permissions
+ clearInternal();
+
+ // run the callback
+ if (this.removalCallback != null) {
+ this.removalCallback.attachmentRemoved(this);
+ }
+
+ // unhook from the permissible
+ this.hooked = false;
+ this.permissible.attachments.remove(this);
+ }
+
+ @Override
+ public void setPermission(String name, boolean value) {
+ Objects.requireNonNull(name, "name is null");
+ Preconditions.checkArgument(!name.isEmpty(), "name is empty");
+
+ String permission = name.toLowerCase();
+
+ Boolean previous = this.perms.put(permission, value);
+ if (previous != null && previous == value) {
+ return;
+ }
+
+ // if we're not hooked, then don't actually apply the change
+ // it will get applied on hook - if that ever happens
+ if (!this.hooked) {
+ return;
+ }
+
+ if (previous != null) {
+ unsetPermissionInternal(permission);
+ }
+
+ setPermissionInternal(permission, value);
+ }
+
+ @Override
+ public void setPermission(Permission permission, boolean value) {
+ setPermission(permission.getName(), value);
+ }
+
+ @Override
+ public void setPermissions(Map permissions) {
+ for (Map.Entry entry : permissions.entrySet()) {
+ setPermission(entry.getKey(), entry.getValue());
+ }
+ }
+
+ @Override
+ public void unsetPermission(String name, boolean value) {
+ Objects.requireNonNull(name, "name is null");
+ Preconditions.checkArgument(!name.isEmpty(), "name is empty");
+
+ String permission = name.toLowerCase();
+
+ Boolean previous = this.perms.remove(permission);
+ if (previous == null) {
+ return;
+ }
+
+ // if we're not hooked, then don't actually apply the change
+ // it will get applied on hook - if that ever happens
+ if (!this.hooked) {
+ return;
+ }
+
+ unsetPermissionInternal(permission);
+ }
+
+ @Override
+ public void unsetPermission(Permission permission, boolean value) {
+ unsetPermission(permission.getName(), value);
+ }
+
+ @Override
+ public void unsetPermissions(List permissions) {
+ for (String perm : permissions) {
+ unsetPermission(perm, true);
+ }
+ }
+
+ @Override
+ public void clearPermissions() {
+ this.perms.clear();
+ clearInternal();
+ }
+
+ @Override
+ public Map getPermissions() {
+ return this.perms;
+ }
+
+ @Override
+ public Plugin getPlugin() {
+ return this.owner != null ? this.owner : this.permissible.getPlugin();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return this == obj;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
+
+ /**
+ * A fake map to be injected into the superclass. This implementation simply
+ * proxies calls back to this attachment instance.
+ *
+ * Some (clever/dumb??) plugins attempt to modify attachment permissions using reflection
+ * to get around the slow nukkit behaviour in the base PermissionAttachment implementation.
+ *
+ * An instance of this map is injected into the super instance so these plugins continue
+ * to work with LuckPerms.
+ */
+ private final class FakeBackingMap implements Map {
+
+ @Override
+ public Boolean put(String key, Boolean value) {
+ // grab the previous result, so we can still satisfy the method signature of Map
+ Boolean previous = LPPermissionAttachment.this.perms.get(key);
+
+ // proxy the call back through the PermissionAttachment instance
+ setPermission(key, value);
+
+ // return the previous value
+ return previous;
+ }
+
+ @Override
+ public Boolean remove(Object key) {
+ // we only accept string keys
+ if (!(key instanceof String)) {
+ return null;
+ }
+
+ String permission = ((String) key);
+
+ // grab the previous result, so we can still satisfy the method signature of Map
+ Boolean previous = LPPermissionAttachment.this.perms.get(permission);
+
+ // proxy the call back through the PermissionAttachment instance
+ unsetPermission(permission, true);
+
+ // return the previous value
+ return previous;
+ }
+
+ @Override
+ public void putAll(Map extends String, ? extends Boolean> m) {
+ for (Map.Entry extends String, ? extends Boolean> entry : m.entrySet()) {
+ put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ @Override
+ public void clear() {
+ // remove the permissions which have already been applied
+ if (LPPermissionAttachment.this.hooked) {
+ clearInternal();
+ }
+
+ // clear the backing map
+ LPPermissionAttachment.this.perms.clear();
+ }
+
+ @Override
+ public int size() {
+ // return the size of the permissions map - probably the most accurate value we have
+ return LPPermissionAttachment.this.perms.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ // return if the permissions map is empty - again probably the most accurate thing
+ // we can return
+ return LPPermissionAttachment.this.perms.isEmpty();
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ // just proxy
+ return LPPermissionAttachment.this.perms.containsKey(key);
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ // just proxy
+ return LPPermissionAttachment.this.perms.containsValue(value);
+ }
+
+ @Override
+ public Boolean get(Object key) {
+ // just proxy
+ return LPPermissionAttachment.this.perms.get(key);
+ }
+
+ @Override
+ public Set keySet() {
+ // just proxy
+ return LPPermissionAttachment.this.perms.keySet();
+ }
+
+ @Override
+ public Collection values() {
+ // just proxy
+ return LPPermissionAttachment.this.perms.values();
+ }
+
+ @Override
+ public Set> entrySet() {
+ // just proxy
+ return LPPermissionAttachment.this.perms.entrySet();
+ }
+ }
+}
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/MonitoredPermissibleBase.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/MonitoredPermissibleBase.java
new file mode 100644
index 000000000..9a506902c
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/MonitoredPermissibleBase.java
@@ -0,0 +1,145 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit.model.permissible;
+
+import me.lucko.luckperms.api.Tristate;
+import me.lucko.luckperms.api.context.ContextSet;
+import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
+import me.lucko.luckperms.common.verbose.CheckOrigin;
+import me.lucko.luckperms.common.verbose.VerboseHandler;
+import me.lucko.luckperms.nukkit.model.dummy.DummyPermissibleBase;
+
+import cn.nukkit.permission.PermissibleBase;
+import cn.nukkit.permission.Permission;
+import cn.nukkit.permission.PermissionAttachment;
+import cn.nukkit.permission.PermissionAttachmentInfo;
+import cn.nukkit.plugin.Plugin;
+
+import java.util.Map;
+
+/**
+ * A PermissibleBase extension which logs permission checks to the
+ * plugin's {@link VerboseHandler} facility.
+ *
+ * Method calls are forwarded to the delegate permissible.
+ */
+public class MonitoredPermissibleBase extends PermissibleBase {
+ private final LuckPermsPlugin plugin;
+ private final PermissibleBase delegate;
+ private final String name;
+
+ // remains false until the object has been constructed
+ // necessary to catch the superclass call to #recalculatePermissions on init
+ @SuppressWarnings("UnusedAssignment")
+ private boolean initialised = false;
+
+ public MonitoredPermissibleBase(LuckPermsPlugin plugin, PermissibleBase delegate, String name) {
+ super(null);
+ DummyPermissibleBase.nullFields(this);
+
+ this.plugin = plugin;
+ this.delegate = delegate;
+ this.name = name;
+ this.initialised = true;
+
+ // since we effectively cancel the execution of this call in the super
+ // constructor we need to call it again.
+ recalculatePermissions();
+ }
+
+ private void logCheck(CheckOrigin origin, String permission, boolean result) {
+ this.plugin.getVerboseHandler().offerCheckData(origin, this.name, ContextSet.empty(), permission, Tristate.fromBoolean(result));
+ this.plugin.getPermissionVault().offer(permission);
+ }
+
+ PermissibleBase getDelegate() {
+ return this.delegate;
+ }
+
+ @Override
+ public boolean isPermissionSet(String permission) {
+ if (permission == null) {
+ throw new NullPointerException("permission");
+ }
+
+ final boolean result = this.delegate.isPermissionSet(permission);
+ logCheck(CheckOrigin.PLATFORM_LOOKUP_CHECK, permission, result);
+ return result;
+ }
+
+ @Override
+ public boolean isPermissionSet(Permission permission) {
+ if (permission == null) {
+ throw new NullPointerException("permission");
+ }
+
+ final boolean result = this.delegate.isPermissionSet(permission);
+ logCheck(CheckOrigin.PLATFORM_LOOKUP_CHECK, permission.getName(), result);
+ return result;
+ }
+
+ @Override
+ public boolean hasPermission(String permission) {
+ if (permission == null) {
+ throw new NullPointerException("permission");
+ }
+
+ final boolean result = this.delegate.hasPermission(permission);
+ logCheck(CheckOrigin.PLATFORM_PERMISSION_CHECK, permission, result);
+ return result;
+ }
+
+ @Override
+ public boolean hasPermission(Permission permission) {
+ if (permission == null) {
+ throw new NullPointerException("permission");
+ }
+
+ final boolean result = this.delegate.hasPermission(permission);
+ logCheck(CheckOrigin.PLATFORM_PERMISSION_CHECK, permission.getName(), result);
+ return result;
+ }
+
+ @Override
+ public void recalculatePermissions() {
+ if (!this.initialised) {
+ return;
+ }
+
+ this.delegate.recalculatePermissions();
+ }
+
+ // just forward calls to the delegate permissible
+ @Override public boolean isOp() { return this.delegate.isOp(); }
+ @Override public void setOp(boolean value) { this.delegate.setOp(value); }
+ @Override public PermissionAttachment addAttachment(Plugin plugin, String name, Boolean value) { return this.delegate.addAttachment(plugin, name, value); }
+ @Override public PermissionAttachment addAttachment(Plugin plugin, String name) { return this.delegate.addAttachment(plugin, name); }
+ @Override public PermissionAttachment addAttachment(Plugin plugin) { return this.delegate.addAttachment(plugin); }
+ @Override public void removeAttachment(PermissionAttachment attachment) { this.delegate.removeAttachment(attachment); }
+ @Override public void clearPermissions() { this.delegate.clearPermissions(); }
+ @Override public Map getEffectivePermissions() { return this.delegate.getEffectivePermissions(); }
+
+}
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/PermissibleInjector.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/PermissibleInjector.java
new file mode 100644
index 000000000..27df5a6b5
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/PermissibleInjector.java
@@ -0,0 +1,146 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit.model.permissible;
+
+import me.lucko.luckperms.nukkit.model.dummy.DummyPermissibleBase;
+
+import cn.nukkit.Player;
+import cn.nukkit.permission.PermissibleBase;
+import cn.nukkit.permission.PermissionAttachment;
+
+import java.lang.reflect.Field;
+import java.util.Set;
+
+/**
+ * Injects a {@link LPPermissible} into a {@link Player}.
+ *
+ * This allows LuckPerms to directly intercept permission checks and take over all handling of
+ * checks made by plugins.
+ */
+public final class PermissibleInjector {
+
+ /**
+ * All permission checks made on standard Nukkit objects are effectively proxied to a
+ * {@link PermissibleBase} object, held as a variable on the object.
+ *
+ * This field is where the permissible is stored on a Player.
+ */
+ private static final Field PLAYER_PERMISSIBLE_FIELD;
+
+ /**
+ * The field where attachments are stored on a permissible base.
+ */
+ private static final Field PERMISSIBLE_BASE_ATTACHMENTS_FIELD;
+
+ static {
+ try {
+ // Try to load the permissible field.
+ PLAYER_PERMISSIBLE_FIELD = Player.class.getDeclaredField("perm");
+ PLAYER_PERMISSIBLE_FIELD.setAccessible(true);
+
+ // Try to load the attachments field.
+ PERMISSIBLE_BASE_ATTACHMENTS_FIELD = PermissibleBase.class.getDeclaredField("attachments");
+ PERMISSIBLE_BASE_ATTACHMENTS_FIELD.setAccessible(true);
+ } catch (NoSuchFieldException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ /**
+ * Injects a {@link LPPermissible} into a {@link Player}.
+ *
+ * @param player the player to inject into
+ * @param newPermissible the permissible to inject
+ * @throws Exception propagates any exceptions which were thrown during injection
+ */
+ public static void inject(Player player, LPPermissible newPermissible) throws Exception {
+
+ // get the existing PermissibleBase held by the player
+ PermissibleBase oldPermissible = (PermissibleBase) PLAYER_PERMISSIBLE_FIELD.get(player);
+
+ // seems we have already injected into this player.
+ if (oldPermissible instanceof LPPermissible) {
+ throw new IllegalStateException("LPPermissible already injected into player " + player.toString());
+ }
+
+ // Move attachments over from the old permissible
+
+ //noinspection unchecked
+ Set attachments = (Set) PERMISSIBLE_BASE_ATTACHMENTS_FIELD.get(oldPermissible);
+
+ newPermissible.convertAndAddAttachments(attachments);
+ attachments.clear();
+ oldPermissible.clearPermissions();
+
+ // Setup the new permissible
+ newPermissible.getActive().set(true);
+ newPermissible.setOldPermissible(oldPermissible);
+
+ // inject the new instance
+ PLAYER_PERMISSIBLE_FIELD.set(player, newPermissible);
+ }
+
+ /**
+ * Uninjects a {@link LPPermissible} from a {@link Player}.
+ *
+ * @param player the player to uninject from
+ * @param dummy if the replacement permissible should be a dummy.
+ * @throws Exception propagates any exceptions which were thrown during uninjection
+ */
+ public static void unInject(Player player, boolean dummy) throws Exception {
+
+ // gets the players current permissible.
+ PermissibleBase permissible = (PermissibleBase) PLAYER_PERMISSIBLE_FIELD.get(player);
+
+ // only uninject if the permissible was a luckperms one.
+ if (permissible instanceof LPPermissible) {
+ LPPermissible lpPermissible = ((LPPermissible) permissible);
+
+ // clear all permissions
+ lpPermissible.clearPermissions();
+
+ // set to inactive
+ lpPermissible.getActive().set(false);
+
+ // handle the replacement permissible.
+ if (dummy) {
+ // just inject a dummy class. this is used when we know the player is about to quit the server.
+ PLAYER_PERMISSIBLE_FIELD.set(player, DummyPermissibleBase.INSTANCE);
+
+ } else {
+ PermissibleBase newPb = lpPermissible.getOldPermissible();
+ if (newPb == null) {
+ newPb = new PermissibleBase(player);
+ }
+
+ PLAYER_PERMISSIBLE_FIELD.set(player, newPb);
+ }
+ }
+ }
+
+ private PermissibleInjector() {}
+
+}
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/PermissibleMonitoringInjector.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/PermissibleMonitoringInjector.java
new file mode 100644
index 000000000..ab8fe7b89
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/PermissibleMonitoringInjector.java
@@ -0,0 +1,84 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit.model.permissible;
+
+import me.lucko.luckperms.nukkit.LPNukkitPlugin;
+
+import cn.nukkit.command.ConsoleCommandSender;
+import cn.nukkit.permission.PermissibleBase;
+
+import java.lang.reflect.Field;
+import java.util.Objects;
+
+/**
+ * Injects {@link MonitoredPermissibleBase}s into non-player permissibles on
+ * the server so their checks can be monitored by the verbose facility.
+ */
+public class PermissibleMonitoringInjector implements Runnable {
+ private final LPNukkitPlugin plugin;
+
+ public PermissibleMonitoringInjector(LPNukkitPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void run() {
+ try {
+ injectConsole();
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+
+ private MonitoredPermissibleBase wrap(PermissibleBase permBase, String name) {
+ Objects.requireNonNull(permBase, "permBase");
+
+ // unwrap any previous injection
+ if (permBase instanceof MonitoredPermissibleBase) {
+ permBase = ((MonitoredPermissibleBase) permBase).getDelegate();
+ }
+
+ // create a monitored instance which delegates to the previous PermissibleBase
+ return new MonitoredPermissibleBase(this.plugin, permBase, name);
+ }
+
+ private void injectConsole() throws Exception {
+ ConsoleCommandSender consoleSender = this.plugin.getServer().getConsoleSender();
+
+ // get the perm field
+ Field permField = ConsoleCommandSender.class.getDeclaredField("perm");
+ permField.setAccessible(true);
+
+ // get the PermissibleBase instance
+ PermissibleBase permBase = (PermissibleBase) permField.get(consoleSender);
+
+ // create a monitored instance which delegates to the previous PermissibleBase
+ MonitoredPermissibleBase newPermBase = wrap(permBase, "internal/console");
+
+ // inject the monitored instance
+ permField.set(consoleSender, newPermBase);
+ }
+}
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/InjectorDefaultsMap.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/InjectorDefaultsMap.java
new file mode 100644
index 000000000..a22c44884
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/InjectorDefaultsMap.java
@@ -0,0 +1,138 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit.model.server;
+
+import com.google.common.collect.ImmutableMap;
+
+import me.lucko.luckperms.nukkit.LPNukkitPlugin;
+
+import cn.nukkit.Server;
+import cn.nukkit.permission.Permission;
+import cn.nukkit.plugin.PluginManager;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Injects a {@link LPDefaultsMap} info the {@link PluginManager}.
+ */
+public class InjectorDefaultsMap implements Runnable {
+ private static final Field OP_DEFAULT_PERMISSIONS_FIELD;
+ private static final Field NON_OP_DEFAULT_PERMISSIONS_FIELD;
+
+ static {
+ Field opPermissionsField = null;
+ try {
+ opPermissionsField = PluginManager.class.getDeclaredField("defaultPermsOp");
+ opPermissionsField.setAccessible(true);
+ } catch (Exception e) {
+ // ignore
+ }
+ OP_DEFAULT_PERMISSIONS_FIELD = opPermissionsField;
+
+ Field nonOpPermissionsField = null;
+ try {
+ nonOpPermissionsField = PluginManager.class.getDeclaredField("defaultPerms");
+ nonOpPermissionsField.setAccessible(true);
+ } catch (Exception e) {
+ // ignore
+ }
+ NON_OP_DEFAULT_PERMISSIONS_FIELD = nonOpPermissionsField;
+ }
+
+ private final LPNukkitPlugin plugin;
+
+ public InjectorDefaultsMap(LPNukkitPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void run() {
+ try {
+ LPDefaultsMap ret = inject();
+ if (ret != null) {
+ this.plugin.setDefaultPermissionMap(ret);
+ }
+ } catch (Exception e) {
+ this.plugin.getLog().severe("Exception occurred whilst injecting LuckPerms Default Permission map.");
+ e.printStackTrace();
+ }
+ }
+
+ private LPDefaultsMap inject() throws Exception {
+ Objects.requireNonNull(OP_DEFAULT_PERMISSIONS_FIELD, "OP_DEFAULT_PERMISSIONS_FIELD");
+ Objects.requireNonNull(NON_OP_DEFAULT_PERMISSIONS_FIELD, "NON_OP_DEFAULT_PERMISSIONS_FIELD");
+
+ PluginManager pluginManager = this.plugin.getServer().getPluginManager();
+
+ Object opMap = OP_DEFAULT_PERMISSIONS_FIELD.get(pluginManager);
+ Object nonOpMap = NON_OP_DEFAULT_PERMISSIONS_FIELD.get(pluginManager);
+
+ if (opMap instanceof LPDefaultsMap.DefaultPermissionSet && ((LPDefaultsMap.DefaultPermissionSet) opMap).parent.plugin == this.plugin) {
+ if (nonOpMap instanceof LPDefaultsMap.DefaultPermissionSet && ((LPDefaultsMap.DefaultPermissionSet) nonOpMap).parent.plugin == this.plugin) {
+ return null;
+ }
+ }
+
+ //noinspection unchecked
+ Map castedOpMap = (Map) opMap;
+
+ //noinspection unchecked
+ Map castedNonOpMap = (Map) nonOpMap;
+
+ // make a new map & inject it
+ LPDefaultsMap newMap = new LPDefaultsMap(this.plugin, ImmutableMap.of(true, castedOpMap, false, castedNonOpMap));
+ OP_DEFAULT_PERMISSIONS_FIELD.set(pluginManager, newMap.getOpPermissions());
+ NON_OP_DEFAULT_PERMISSIONS_FIELD.set(pluginManager, newMap.getNonOpPermissions());
+ return newMap;
+ }
+
+ public static void uninject() {
+ try {
+ Objects.requireNonNull(OP_DEFAULT_PERMISSIONS_FIELD, "OP_DEFAULT_PERMISSIONS_FIELD");
+ Objects.requireNonNull(NON_OP_DEFAULT_PERMISSIONS_FIELD, "NON_OP_DEFAULT_PERMISSIONS_FIELD");
+ PluginManager pluginManager = Server.getInstance().getPluginManager();
+ {
+ Object map = OP_DEFAULT_PERMISSIONS_FIELD.get(pluginManager);
+ if (map instanceof LPDefaultsMap.DefaultPermissionSet) {
+ LPDefaultsMap.DefaultPermissionSet lpMap = (LPDefaultsMap.DefaultPermissionSet) map;
+ OP_DEFAULT_PERMISSIONS_FIELD.set(pluginManager, new HashMap<>(lpMap));
+ }
+ }
+ {
+ Object map = NON_OP_DEFAULT_PERMISSIONS_FIELD.get(pluginManager);
+ if (map instanceof LPDefaultsMap.DefaultPermissionSet) {
+ LPDefaultsMap.DefaultPermissionSet lpMap = (LPDefaultsMap.DefaultPermissionSet) map;
+ NON_OP_DEFAULT_PERMISSIONS_FIELD.set(pluginManager, new HashMap<>(lpMap));
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/InjectorPermissionMap.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/InjectorPermissionMap.java
new file mode 100644
index 000000000..7b040b9fa
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/InjectorPermissionMap.java
@@ -0,0 +1,108 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit.model.server;
+
+import me.lucko.luckperms.nukkit.LPNukkitPlugin;
+
+import cn.nukkit.Server;
+import cn.nukkit.permission.Permission;
+import cn.nukkit.plugin.PluginManager;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Injects a {@link LPPermissionMap} into the {@link PluginManager}.
+ */
+public class InjectorPermissionMap implements Runnable {
+ private static final Field PERMISSIONS_FIELD;
+
+ static {
+ Field permissionsField = null;
+ try {
+ permissionsField = PluginManager.class.getDeclaredField("permissions");
+ permissionsField.setAccessible(true);
+ } catch (Exception e) {
+ // ignore
+ }
+ PERMISSIONS_FIELD = permissionsField;
+ }
+
+ private final LPNukkitPlugin plugin;
+
+ public InjectorPermissionMap(LPNukkitPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void run() {
+ try {
+ LPPermissionMap ret = inject();
+ if (ret != null) {
+ this.plugin.setPermissionMap(ret);
+ }
+ } catch (Exception e) {
+ this.plugin.getLog().severe("Exception occurred whilst injecting LuckPerms Permission map.");
+ e.printStackTrace();
+ }
+ }
+
+ private LPPermissionMap inject() throws Exception {
+ Objects.requireNonNull(PERMISSIONS_FIELD, "PERMISSIONS_FIELD");
+ PluginManager pluginManager = this.plugin.getServer().getPluginManager();
+
+ Object map = PERMISSIONS_FIELD.get(pluginManager);
+ if (map instanceof LPPermissionMap && ((LPPermissionMap) map).plugin == this.plugin) {
+ return null;
+ }
+
+ //noinspection unchecked
+ Map castedMap = (Map) map;
+
+ // make a new map & inject it
+ LPPermissionMap newMap = new LPPermissionMap(this.plugin, castedMap);
+ PERMISSIONS_FIELD.set(pluginManager, newMap);
+ return newMap;
+ }
+
+ public static void uninject() {
+ try {
+ Objects.requireNonNull(PERMISSIONS_FIELD, "PERMISSIONS_FIELD");
+ PluginManager pluginManager = Server.getInstance().getPluginManager();
+
+ Object map = PERMISSIONS_FIELD.get(pluginManager);
+ if (map instanceof LPPermissionMap) {
+ LPPermissionMap lpMap = (LPPermissionMap) map;
+ PERMISSIONS_FIELD.set(pluginManager, new HashMap<>(lpMap));
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/InjectorSubscriptionMap.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/InjectorSubscriptionMap.java
new file mode 100644
index 000000000..a6e698f09
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/InjectorSubscriptionMap.java
@@ -0,0 +1,112 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit.model.server;
+
+import me.lucko.luckperms.nukkit.LPNukkitPlugin;
+
+import cn.nukkit.Server;
+import cn.nukkit.permission.Permissible;
+import cn.nukkit.plugin.PluginManager;
+
+import java.lang.reflect.Field;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Injects a {@link LPSubscriptionMap} into the {@link PluginManager}.
+ */
+public class InjectorSubscriptionMap implements Runnable {
+ private static final Field PERM_SUBS_FIELD;
+
+ static {
+ Field permSubsField = null;
+ try {
+ permSubsField = PluginManager.class.getDeclaredField("permSubs");
+ permSubsField.setAccessible(true);
+ } catch (Exception e) {
+ // ignore
+ }
+ PERM_SUBS_FIELD = permSubsField;
+ }
+
+ private final LPNukkitPlugin plugin;
+
+ public InjectorSubscriptionMap(LPNukkitPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void run() {
+ try {
+ LPSubscriptionMap ret = inject();
+ if (ret != null) {
+ this.plugin.setSubscriptionMap(ret);
+ }
+ } catch (Exception e) {
+ this.plugin.getLog().severe("Exception occurred whilst injecting LuckPerms Permission Subscription map.");
+ e.printStackTrace();
+ }
+ }
+
+ private LPSubscriptionMap inject() throws Exception {
+ Objects.requireNonNull(PERM_SUBS_FIELD, "PERM_SUBS_FIELD");
+ PluginManager pluginManager = this.plugin.getServer().getPluginManager();
+
+ Object map = PERM_SUBS_FIELD.get(pluginManager);
+ if (map instanceof LPSubscriptionMap) {
+ if (((LPSubscriptionMap) map).plugin == this.plugin) {
+ return null;
+ }
+
+ map = ((LPSubscriptionMap) map).detach();
+ }
+
+ //noinspection unchecked
+ Map> castedMap = (Map>) map;
+
+ // make a new subscription map & inject it
+ LPSubscriptionMap newMap = new LPSubscriptionMap(this.plugin, castedMap);
+ PERM_SUBS_FIELD.set(pluginManager, newMap);
+ return newMap;
+ }
+
+ public static void uninject() {
+ try {
+ Objects.requireNonNull(PERM_SUBS_FIELD, "PERM_SUBS_FIELD");
+ PluginManager pluginManager = Server.getInstance().getPluginManager();
+
+ Object map = PERM_SUBS_FIELD.get(pluginManager);
+ if (map instanceof LPSubscriptionMap) {
+ LPSubscriptionMap lpMap = (LPSubscriptionMap) map;
+ PERM_SUBS_FIELD.set(pluginManager, lpMap.detach());
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/LPDefaultsMap.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/LPDefaultsMap.java
new file mode 100644
index 000000000..218cc887c
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/LPDefaultsMap.java
@@ -0,0 +1,166 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit.model.server;
+
+import com.google.common.collect.ForwardingMap;
+import com.google.common.collect.ImmutableMap;
+
+import me.lucko.luckperms.api.Tristate;
+import me.lucko.luckperms.nukkit.LPNukkitPlugin;
+
+import cn.nukkit.permission.Permission;
+import cn.nukkit.plugin.PluginManager;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A replacement map for the 'defaultPerms' instance in Nukkit's SimplePluginManager.
+ *
+ * This instance allows LuckPerms to intercept calls to
+ * {@link PluginManager#addPermission(Permission)}, specifically regarding
+ * the default nature of the permission.
+ *
+ * Injected by {@link InjectorDefaultsMap}.
+ */
+public final class LPDefaultsMap {
+
+ // the plugin
+ final LPNukkitPlugin plugin;
+
+ // the two values in the map
+ private final Map opSet = new DefaultPermissionSet(true);
+ private final Map nonOpSet = new DefaultPermissionSet(false);
+
+ // fully resolved defaults (accounts for child permissions too)
+ private Map resolvedOpDefaults = ImmutableMap.of();
+ private Map resolvedNonOpDefaults = ImmutableMap.of();
+
+ public LPDefaultsMap(LPNukkitPlugin plugin, Map> existingData) {
+ this.plugin = plugin;
+ this.opSet.putAll(existingData.getOrDefault(Boolean.TRUE, Collections.emptyMap()));
+ this.nonOpSet.putAll(existingData.getOrDefault(Boolean.FALSE, Collections.emptyMap()));
+ refreshOp();
+ refreshNonOp();
+ }
+
+ public Map getOpPermissions() {
+ return this.opSet;
+ }
+
+ public Map getNonOpPermissions() {
+ return this.nonOpSet;
+ }
+
+ /**
+ * Queries whether a given permission should be granted by default.
+ *
+ * @param permission the permission to query
+ * @param isOp if the player is op
+ * @return a tristate result
+ */
+ public Tristate lookupDefaultPermission(String permission, boolean isOp) {
+ Map map = isOp ? this.resolvedOpDefaults : this.resolvedNonOpDefaults;
+ return Tristate.fromNullableBoolean(map.get(permission));
+ }
+
+ private void refresh(boolean op) {
+ if (op) {
+ refreshOp();
+ } else {
+ refreshNonOp();
+ }
+ }
+
+ /**
+ * Refreshes the op data in this provider.
+ */
+ private void refreshOp() {
+ Map builder = new HashMap<>();
+ for (Permission perm : getOpPermissions().values()) {
+ String name = perm.getName().toLowerCase();
+ builder.put(name, true);
+ for (Map.Entry child : this.plugin.getPermissionMap().getChildPermissions(name, true).entrySet()) {
+ builder.putIfAbsent(child.getKey(), child.getValue());
+ }
+ }
+ this.resolvedOpDefaults = ImmutableMap.copyOf(builder);
+ }
+
+ /**
+ * Refreshes the non op data in this provider.
+ */
+ private void refreshNonOp() {
+ Map builder = new HashMap<>();
+ for (Permission perm : getNonOpPermissions().values()) {
+ String name = perm.getName().toLowerCase();
+ builder.put(name, true);
+ for (Map.Entry child : this.plugin.getPermissionMap().getChildPermissions(name, true).entrySet()) {
+ builder.putIfAbsent(child.getKey(), child.getValue());
+ }
+ }
+ this.resolvedNonOpDefaults = ImmutableMap.copyOf(builder);
+ }
+
+ final class DefaultPermissionSet extends ForwardingMap {
+ final LPDefaultsMap parent = LPDefaultsMap.this;
+
+ private final Map delegate = new ConcurrentHashMap<>();
+ private final boolean op;
+
+ private DefaultPermissionSet(boolean op) {
+ this.op = op;
+ }
+
+ @Override
+ protected Map delegate() {
+ return this.delegate;
+ }
+
+ @Override
+ public Permission put(String key, Permission value) {
+ Permission ret = super.put(key, value);
+ refresh(this.op);
+ return ret;
+ }
+
+ @Override
+ public Permission putIfAbsent(String key, Permission value) {
+ Permission ret = super.putIfAbsent(key, value);
+ refresh(this.op);
+ return ret;
+ }
+
+ @Override
+ public void putAll(Map extends String, ? extends Permission> map) {
+ super.putAll(map);
+ refresh(this.op);
+ }
+ }
+
+}
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/LPPermissionMap.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/LPPermissionMap.java
new file mode 100644
index 000000000..65c013bef
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/LPPermissionMap.java
@@ -0,0 +1,155 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit.model.server;
+
+import com.github.benmanes.caffeine.cache.CacheLoader;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.LoadingCache;
+import com.google.common.collect.ForwardingMap;
+import com.google.common.collect.ImmutableMap;
+
+import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
+import me.lucko.luckperms.common.treeview.PermissionVault;
+
+import cn.nukkit.permission.Permission;
+import cn.nukkit.plugin.PluginManager;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/**
+ * A replacement map for the 'permissions' instance in Nukkit's SimplePluginManager.
+ *
+ * This instance allows LuckPerms to intercept calls to
+ * {@link PluginManager#addPermission(Permission)} and record permissions in the
+ * {@link PermissionVault}.
+ *
+ * It also allows us to pre-determine child permission relationships.
+ *
+ * Injected by {@link InjectorPermissionMap}.
+ */
+public final class LPPermissionMap extends ForwardingMap {
+
+ // Uses perm.getName().toLowerCase(java.util.Locale.ENGLISH); to determine the key
+ private final Map delegate = new ConcurrentHashMap<>();
+
+ // cache from permission --> children
+ private final LoadingCache> trueChildPermissions = Caffeine.newBuilder()
+ .build(new ChildPermissionResolver(true));
+
+ private final LoadingCache> falseChildPermissions = Caffeine.newBuilder()
+ .build(new ChildPermissionResolver(false));
+
+ /**
+ * The plugin instance
+ */
+ final LuckPermsPlugin plugin;
+
+ public LPPermissionMap(LuckPermsPlugin plugin, Map existingData) {
+ this.plugin = plugin;
+ putAll(existingData);
+ }
+
+ public Map getChildPermissions(String permission, boolean value) {
+ return value ? this.trueChildPermissions.get(permission) : this.falseChildPermissions.get(permission);
+ }
+
+ private void update() {
+ this.trueChildPermissions.invalidateAll();
+ this.falseChildPermissions.invalidateAll();
+ }
+
+ @Override
+ protected Map delegate() {
+ return this.delegate;
+ }
+
+ @Override
+ public Permission put(@Nonnull String key, @Nonnull Permission value) {
+ this.plugin.getPermissionVault().offer(key);
+ Permission ret = super.put(key, value);
+ update();
+ return ret;
+ }
+
+ @Override
+ public void putAll(@Nonnull Map extends String, ? extends Permission> m) {
+ this.plugin.getPermissionVault().offerAll(m.keySet());
+ super.putAll(m);
+ update();
+ }
+
+ @Override
+ public Permission putIfAbsent(String key, Permission value) {
+ this.plugin.getPermissionVault().offer(key);
+ Permission ret = super.putIfAbsent(key, value);
+ update();
+ return ret;
+ }
+
+ private final class ChildPermissionResolver implements CacheLoader> {
+ private final boolean value;
+
+ private ChildPermissionResolver(boolean value) {
+ this.value = value;
+ }
+
+ @CheckForNull
+ @Override
+ public Map load(@Nonnull String key) {
+ Map children = new HashMap<>();
+ resolveChildren(children, Collections.singletonMap(key, this.value), false);
+ children.remove(key, this.value);
+ return ImmutableMap.copyOf(children);
+ }
+ }
+
+ private void resolveChildren(Map accumulator, Map children, boolean invert) {
+ // iterate through the current known children.
+ // the first time this method is called for a given permission, the children map will contain only the permission itself.
+ for (Map.Entry e : children.entrySet()) {
+ if (accumulator.containsKey(e.getKey())) {
+ continue; // Prevent infinite loops
+ }
+
+ // xor the value using the parent (nukkit logic, not mine)
+ boolean value = e.getValue() ^ invert;
+ accumulator.put(e.getKey().toLowerCase(), value);
+
+ // lookup any deeper children & resolve if present
+ Permission perm = this.delegate.get(e.getKey());
+ if (perm != null) {
+ resolveChildren(accumulator, perm.getChildren(), !value);
+ }
+ }
+ }
+
+}
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/LPSubscriptionMap.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/LPSubscriptionMap.java
new file mode 100644
index 000000000..199cb70c4
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/LPSubscriptionMap.java
@@ -0,0 +1,259 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit.model.server;
+
+import com.google.common.collect.Sets;
+
+import me.lucko.luckperms.nukkit.LPNukkitPlugin;
+
+import cn.nukkit.permission.Permissible;
+import cn.nukkit.plugin.PluginManager;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.stream.Collectors;
+
+/**
+ * A replacement map for the 'permSubs' instance in Nukkit's SimplePluginManager.
+ *
+ * This instance allows LuckPerms to intercept calls to
+ * {@link PluginManager#subscribeToPermission(String, Permissible)},
+ * {@link PluginManager#unsubscribeFromPermission(String, Permissible)} and
+ * {@link PluginManager#getPermissionSubscriptions(String)}.
+ *
+ * Nukkit for some reason sometimes uses subscription status to determine whether
+ * a permissible has a given node, instead of checking directly with
+ * {@link Permissible#hasPermission(String)}.
+ *
+ * In order to implement predicable Nukkit behaviour, LP has two options:
+ * 1) register subscriptions for all players as normal, or
+ * 2) inject it's own map instance to proxy calls to {@link PluginManager#getPermissionSubscriptions(String)} back to LuckPerms.
+ *
+ * This class implements option 2 above. It is preferred because it is faster & uses less memory
+ *
+ * Injected by {@link InjectorSubscriptionMap}.
+ */
+public final class LPSubscriptionMap extends HashMap> {
+
+ // the plugin instance
+ final LPNukkitPlugin plugin;
+
+ public LPSubscriptionMap(LPNukkitPlugin plugin, Map> existingData) {
+ super(existingData);
+ this.plugin = plugin;
+ }
+
+ /*
+ * The get method is the only one which is actually used by SimplePluginManager
+ * we override it to always return a value - which means the null check in
+ * subscribeToDefaultPerms always fails - soo, we don't have to worry too much
+ * about implementing #put.
+ *
+ * we also ensure all returns are LPSubscriptionValueMaps. this extension
+ * will also delegate checks to online players - meaning we don't ever
+ * have to register their subscriptions with the plugin manager.
+ */
+ @Override
+ public Set get(Object key) {
+ if (key == null || !(key instanceof String)) {
+ return null;
+ }
+
+ String permission = ((String) key);
+
+ Set result = super.get(key);
+
+ if (result == null) {
+ // calculate a new map - always!
+ result = new LPSubscriptionValueSet(permission);
+ super.put(permission, result);
+ } else if (!(result instanceof LPSubscriptionValueSet)) {
+ // ensure return type is a LPSubscriptionMap
+ result = new LPSubscriptionValueSet(permission, result);
+ super.put(permission, result);
+ }
+ return result;
+ }
+
+ @Override
+ public Set put(String key, Set value) {
+ if (value == null) {
+ throw new NullPointerException("Map value cannot be null");
+ }
+
+ // ensure values are LP subscription maps
+ if (!(value instanceof LPSubscriptionValueSet)) {
+ value = new LPSubscriptionValueSet(key, value);
+ }
+ return super.put(key, value);
+ }
+
+ // if the key isn't null and is a string, #get will always return a value for it
+ @Override
+ public boolean containsKey(Object key) {
+ return key != null && key instanceof String;
+ }
+
+ /**
+ * Converts this map back to a standard HashMap
+ *
+ * @return a standard representation of this map
+ */
+ public Map> detach() {
+ Map> ret = new HashMap<>();
+
+ for (Map.Entry> ent : entrySet()) {
+ if (ent.getValue() instanceof LPSubscriptionValueSet) {
+ Set backing = ((LPSubscriptionValueSet) ent.getValue()).backing;
+ Set copy; (copy = Collections.newSetFromMap(new WeakHashMap<>(backing.size()))).addAll(backing);
+ ret.put(ent.getKey(), copy);
+ } else {
+ ret.put(ent.getKey(), ent.getValue());
+ }
+ }
+
+ return ret;
+ }
+
+ /**
+ * Value map extension which includes LP objects in Permissible related queries.
+ */
+ public final class LPSubscriptionValueSet implements Set {
+
+ // the permission being mapped to this value map
+ private final String permission;
+
+ // the backing map
+ private final Set backing;
+
+ private LPSubscriptionValueSet(String permission, Set content) {
+ this.permission = permission;
+ this.backing = Collections.newSetFromMap(new WeakHashMap<>());
+
+ if (content != null) {
+ this.backing.addAll(content);
+ }
+ }
+
+ private LPSubscriptionValueSet(String permission) {
+ this(permission, null);
+ }
+
+ private Sets.SetView getContentView() {
+ // gather players (LPPermissibles)
+ Set players = LPSubscriptionMap.this.plugin.getServer().getOnlinePlayers().values().stream()
+ .filter(player -> player.isPermissionSet(this.permission))
+ .collect(Collectors.toSet());
+
+ return Sets.union(players, this.backing);
+ }
+
+ @Override
+ public boolean contains(Object key) {
+ // try the backing map
+ if (this.backing.contains(key)) {
+ return true;
+ }
+
+ // then try the permissible
+ if (key instanceof Permissible) {
+ Permissible p = (Permissible) key;
+ return p.isPermissionSet(this.permission);
+ }
+
+ // no result
+ return false;
+ }
+
+
+ @Override
+ public boolean isEmpty() {
+ // we never want to remove this map from the parent - since it just gets recreated
+ // on subsequent calls
+ return false;
+ }
+
+ @Override
+ public int size() {
+ return Math.max(1, this.backing.size());
+ }
+
+ @Override
+ public Iterator iterator() {
+ return getContentView().iterator();
+ }
+
+ @Override
+ public Object[] toArray() {
+ return getContentView().toArray();
+ }
+
+ @Override
+ public T[] toArray(T[] a) {
+ return getContentView().toArray(a);
+ }
+
+ @Override
+ public boolean add(Permissible permissible) {
+ return this.backing.add(permissible);
+ }
+
+ @Override
+ public boolean addAll(Collection extends Permissible> c) {
+ return this.backing.addAll(c);
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ return this.backing.remove(o);
+ }
+
+ @Override
+ public boolean containsAll(Collection> c) {
+ return getContentView().containsAll(c);
+ }
+
+ @Override
+ public boolean retainAll(Collection> c) {
+ return this.backing.retainAll(c);
+ }
+
+ @Override
+ public boolean removeAll(Collection> c) {
+ return this.backing.removeAll(c);
+ }
+
+ @Override
+ public void clear() {
+ this.backing.clear();
+ }
+ }
+}
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/processors/ChildProcessor.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/processors/ChildProcessor.java
new file mode 100644
index 000000000..9e2a658ad
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/processors/ChildProcessor.java
@@ -0,0 +1,64 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit.processors;
+
+import me.lucko.luckperms.api.Tristate;
+import me.lucko.luckperms.common.processors.AbstractPermissionProcessor;
+import me.lucko.luckperms.common.processors.PermissionProcessor;
+import me.lucko.luckperms.nukkit.LPNukkitPlugin;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Permission Processor for Nukkits "child" permission system.
+ */
+public class ChildProcessor extends AbstractPermissionProcessor implements PermissionProcessor {
+ private final LPNukkitPlugin plugin;
+ private Map childPermissions = Collections.emptyMap();
+
+ public ChildProcessor(LPNukkitPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public Tristate hasPermission(String permission) {
+ return Tristate.fromNullableBoolean(this.childPermissions.get(permission));
+ }
+
+ @Override
+ public void refresh() {
+ Map builder = new ConcurrentHashMap<>();
+ for (Map.Entry e : this.sourceMap.entrySet()) {
+ Map children = this.plugin.getPermissionMap().getChildPermissions(e.getKey(), e.getValue());
+ if (children != null) {
+ builder.putAll(children);
+ }
+ }
+ this.childPermissions = builder;
+ }
+}
diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/processors/DefaultsProcessor.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/processors/DefaultsProcessor.java
new file mode 100644
index 000000000..423191c5c
--- /dev/null
+++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/processors/DefaultsProcessor.java
@@ -0,0 +1,55 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.nukkit.processors;
+
+import me.lucko.luckperms.api.Tristate;
+import me.lucko.luckperms.common.processors.PermissionProcessor;
+import me.lucko.luckperms.nukkit.LPNukkitPlugin;
+import me.lucko.luckperms.nukkit.model.PermissionDefault;
+
+/**
+ * Permission Processor for Nukkits "default" permission system.
+ */
+public class DefaultsProcessor implements PermissionProcessor {
+ private final LPNukkitPlugin plugin;
+ private final boolean isOp;
+
+ public DefaultsProcessor(LPNukkitPlugin plugin, boolean isOp) {
+ this.plugin = plugin;
+ this.isOp = isOp;
+ }
+
+ @Override
+ public Tristate hasPermission(String permission) {
+ Tristate t = this.plugin.getDefaultPermissionMap().lookupDefaultPermission(permission, this.isOp);
+ if (t != Tristate.UNDEFINED) {
+ return t;
+ }
+
+ PermissionDefault def = PermissionDefault.fromPermission(this.plugin.getPermissionMap().get(permission));
+ return def == null ? Tristate.UNDEFINED : Tristate.fromBoolean(def.getValue(this.isOp));
+ }
+}
diff --git a/nukkit/src/main/resources/config.yml b/nukkit/src/main/resources/config.yml
new file mode 100644
index 000000000..068844748
--- /dev/null
+++ b/nukkit/src/main/resources/config.yml
@@ -0,0 +1,459 @@
+####################################################################################################
+# +----------------------------------------------------------------------------------------------+ #
+# | __ __ ___ __ __ | #
+# | | | | / ` |__/ |__) |__ |__) |\/| /__` | #
+# | |___ \__/ \__, | \ | |___ | \ | | .__/ | #
+# | | #
+# | | #
+# | SOURCE CODE: https://github.com/lucko/LuckPerms | #
+# | WIKI: https://github.com/lucko/LuckPerms/wiki | #
+# | BUG REPORTS: https://github.com/lucko/LuckPerms/issues | #
+# | | #
+# | Each option in this file is documented and explained here: | #
+# | ==> https://github.com/lucko/LuckPerms/wiki/Configuration | #
+# | | #
+# | New options are not added to this file automatically. Default values are used if an | #
+# | option cannot be found. The latest config versions can be obtained at the link above. | #
+# +----------------------------------------------------------------------------------------------+ #
+####################################################################################################
+
+# +----------------------------------------------------------------------------------------------+ #
+# | General | #
+# +----------------------------------------------------------------------------------------------+ #
+
+# The name of the server, used for server specific permissions. Set to 'global' to disable.
+server: global
+
+# If users on this server should have their global permissions applied.
+# If set to false, only server specific permissions will apply for users on this server
+include-global: true
+
+# If users on this server should have their global world permissions applied.
+# If set to false, only world specific permissions will apply for users on this server
+include-global-world: true
+
+# If users on this server should have global (non-server specific) groups applied
+apply-global-groups: true
+
+# If users on this server should have global (non-world specific) groups applied
+apply-global-world-groups: true
+
+# If the servers own UUID cache/lookup facility should be used when there is no record for a player
+# in the LuckPerms cache.
+use-server-uuid-cache: false
+
+# If LuckPerms should use the "server-name" property from the "server.properties"
+# file as the "server" option within LuckPerms.
+use-server-properties-name: false
+
+# If set to true, LuckPerms will allow usernames with non alphanumeric characters.
+#
+# Note that due to the design of the storage implementation, usernames must still be
+# 16 characters or less.
+allow-invalid-usernames: false
+
+# If LuckPerms should produce extra logging output when it handles logins.
+# Useful if you're having issues with UUID forwarding or data not being loaded.
+debug-logins: false
+
+# If the plugin should send log notifications to users whenever permissions are modified.
+log-notify: true
+
+# Mirrors world names. Whenever LuckPerms checks what world a user is in, if the world name is in
+# this list, the value assigned will be sent forward for permission calculation instead.
+world-rewrite:
+# world_nether: world
+# world_the_end: world
+
+# Controls how temporary permissions/parents/meta should be accumulated
+#
+# The default behaviour is "deny"
+# If "accumulate": durations will be added to the existing expiry time
+# If "replace": durations will be replaced if the new duration is later than the current expiration
+# If "deny": the command will just fail if you try to add another node with the same expiry
+temporary-add-behaviour: deny
+
+# How should LuckPerms determine a users "primary" group.
+#
+# Available Options:
+# -> stored use the value stored against the users record in the file/database
+# -> parents-by-weight just use the users most highly weighted parent
+# -> all-parents-by-weight same as above, but calculates based upon all parents inherited from both
+# directly and indirectly
+primary-group-calculation: parents-by-weight
+
+# If set to false, the plugin will allow a Users primary group to be removed with the
+# 'parent remove' command, and will set their primary group back to default.
+prevent-primary-group-removal: false
+
+# If the plugin should check for "extra" permissions with users run LP commands.
+#
+# These extra permissions allow finer control over what users can do with each command, and
+# who they have access to edit.
+#
+# The permissions are *not* static, unlike the 'base' permisssions, and will depend upon the
+# arguments given within the command.
+argument-based-command-permissions: false
+
+
+
+
+# +----------------------------------------------------------------------------------------------+ #
+# | Permission Calculation | #
+# +----------------------------------------------------------------------------------------------+ #
+
+# If the plugin should apply wildcard permissions.
+# If set to true, LuckPerms will detect wildcard permissions, and resolve & apply all registered
+# permissions matching the wildcard.
+apply-wildcards: true
+
+# If the plugin should parse regex permissions.
+# If set to true, LuckPerms will detect regex permissions, marked with "r=" at the start of the
+# node, and resolve & apply all registered permissions matching the regex.
+apply-regex: true
+
+# If the plugin should complete and apply shorthand permissions.
+# If set to true, LuckPerms will detect and expand shorthand node patterns.
+apply-shorthand: true
+
+# If the plugin should apply Nukkit child permissions.
+# Plugin authors can define custom permissions structures for their plugin, which will be resolved
+# and used by LuckPerms if this setting is enabled.
+apply-nukkit-child-permissions: true
+
+# If the plugin should apply Nukkit default permissions.
+# Plugin authors can define permissions which should be given to all users by default, or setup
+# permissions which should/shouldn't be given to opped players.
+# If this option is set to false, LuckPerms will ignore these defaults.
+apply-nukkit-default-permissions: true
+
+# If the plugin should apply attachment permissions.
+# Other plugins on the server are able to add their own "permission attachments" to players. This
+# allows them to grant players additional permissions which last until the end of the session, or
+# until they're removed. If this option is set to false, LuckPerms will not include these attachment
+# permissions when considering if a player should have access to a certain permission.
+apply-nukkit-attachment-permissions: true
+
+# The algorithm LuckPerms should use when traversing the "inheritance tree".
+#
+# The valid options are:
+# - breadth-first
+# - depth-first-pre-order
+# - depth-first-post-order
+#
+# See here for information about the differences between each algorithm.
+# - https://en.wikipedia.org/wiki/Breadth-first_search
+# - https://en.wikipedia.org/wiki/Depth-first_search
+inheritance-traversal-algorithm: depth-first-pre-order
+
+# Define special group weights for this server.
+# Default is just 0.
+group-weight:
+# admin: 10
+
+
+
+
+# +----------------------------------------------------------------------------------------------+ #
+# | Meta Formatting & Stacking | #
+# +----------------------------------------------------------------------------------------------+ #
+
+# Allows you to setup prefix/suffix stacking options.
+#
+# Available formats:
+# - highest
+# - lowest
+# - highest_own
+# - lowest_own
+# - highest_inherited
+# - lowest_inherited
+# - highest_on_track_