Port to NeoForge (#3950)

This commit is contained in:
Jason Penilla 2024-08-12 11:11:27 -07:00 committed by GitHub
parent b21753f5e9
commit 642f1d3019
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
42 changed files with 3887 additions and 4 deletions

5
.gitattributes vendored Normal file
View File

@ -0,0 +1,5 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# These are explicitly windows files and should use crlf
*.bat text eol=crlf

View File

@ -76,6 +76,7 @@ public interface Platform {
NUKKIT("Nukkit"),
VELOCITY("Velocity"),
FABRIC("Fabric"),
NEOFORGE("NeoForge"),
FORGE("Forge"),
STANDALONE("Standalone");

View File

@ -1,6 +1,7 @@
[versions]
shadow = "8.1.7"
blossom = "1.3.1"
moddevgradle = "1.0.17"
forgegradle = "[6.0,6.2)"
loom = "1.6-SNAPSHOT"
licenser = "0.6.1"
@ -8,6 +9,7 @@ licenser = "0.6.1"
[plugins]
blossom = { id = "net.kyori.blossom", version.ref = "blossom" }
shadow = { id = "io.github.goooler.shadow", version.ref = "shadow" }
moddevgradle = { id = "net.neoforged.moddev", version.ref = "moddevgradle" }
forgegradle = { id = "net.minecraftforge.gradle", version.ref = "forgegradle" }
loom = { id = "fabric-loom", version.ref = "loom" }
licenser = { id = "org.cadixdev.licenser", version.ref = "licenser" }

Binary file not shown.

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

7
gradlew vendored
View File

@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@ -84,7 +86,8 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum

2
gradlew.bat vendored
View File

@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################

63
neoforge/build.gradle Normal file
View File

@ -0,0 +1,63 @@
plugins {
alias(libs.plugins.blossom)
alias(libs.plugins.shadow)
alias(libs.plugins.moddevgradle)
}
sourceCompatibility = 17
targetCompatibility = 21
Configuration shade = configurations.create('shade')
configurations.implementation {
extendsFrom configurations.shade
}
blossom {
replaceTokenIn 'src/main/java/me/lucko/luckperms/neoforge/LPNeoForgeBootstrap.java'
replaceToken '@version@', project.ext.fullVersion
}
neoForge {
version = project.neoForgeVersion
validateAccessTransformers = true
}
dependencies {
add('shade', project(':common'))
compileOnly project(':common:loader-utils')
compileOnly project(':neoforge:neoforge-api')
}
shadowJar {
archiveFileName = "luckperms-neoforge.jarinjar"
configurations = [shade]
dependencies {
include(dependency('me.lucko.luckperms:.*'))
}
relocate 'net.kyori.adventure', 'me.lucko.luckperms.lib.adventure'
relocate 'net.kyori.event', 'me.lucko.luckperms.lib.eventbus'
relocate 'com.github.benmanes.caffeine', 'me.lucko.luckperms.lib.caffeine'
relocate 'okio', 'me.lucko.luckperms.lib.okio'
relocate 'okhttp3', 'me.lucko.luckperms.lib.okhttp3'
relocate 'net.bytebuddy', 'me.lucko.luckperms.lib.bytebuddy'
relocate 'me.lucko.commodore', 'me.lucko.luckperms.lib.commodore'
relocate 'org.mariadb.jdbc', 'me.lucko.luckperms.lib.mariadb'
relocate 'com.mysql', 'me.lucko.luckperms.lib.mysql'
relocate 'org.postgresql', 'me.lucko.luckperms.lib.postgresql'
relocate 'com.zaxxer.hikari', 'me.lucko.luckperms.lib.hikari'
relocate 'com.mongodb', 'me.lucko.luckperms.lib.mongodb'
relocate 'org.bson', 'me.lucko.luckperms.lib.bson'
relocate 'redis.clients.jedis', 'me.lucko.luckperms.lib.jedis'
relocate 'io.nats.client', 'me.lucko.luckperms.lib.nats'
relocate 'com.rabbitmq', 'me.lucko.luckperms.lib.rabbitmq'
relocate 'org.apache.commons.pool2', 'me.lucko.luckperms.lib.commonspool2'
relocate 'ninja.leaping.configurate', 'me.lucko.luckperms.lib.configurate'
relocate 'org.yaml.snakeyaml', 'me.lucko.luckperms.lib.yaml'
}
artifacts {
archives shadowJar
}

View File

@ -0,0 +1,2 @@
minecraftVersion=1.21
neoForgeVersion=21.0.161

View File

@ -0,0 +1,93 @@
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import net.neoforged.moddevgradle.internal.RunGameTask
plugins {
alias(libs.plugins.shadow)
alias(libs.plugins.moddevgradle)
id("java-library")
}
sourceCompatibility = 17
targetCompatibility = 21
neoForge {
version = project.neoForgeVersion
validateAccessTransformers = true
runs {
client {
client()
mods.set(new HashSet()) // Work around classpath issues by using the production jar for dev runs
}
server {
server()
mods.set(new HashSet()) // Work around classpath issues by using the production jar for dev runs
}
}
}
// Work around classpath issues by using the production jar for dev runs
tasks.withType(RunGameTask).configureEach {
dependsOn(tasks.shadowJar)
doFirst {
File jar = file("run/mods/main.jar")
jar.parentFile.mkdirs()
Files.copy(tasks.shadowJar.archiveFile.get().asFile.toPath(), jar.toPath(), StandardCopyOption.REPLACE_EXISTING)
}
}
Configuration shade = configurations.create('shade')
configurations.implementation {
extendsFrom configurations.shade
}
dependencies {
add('shade', project(':api'))
add('shade', project(':common:loader-utils'))
add('shade', project(':neoforge:neoforge-api'))
}
build {
dependsOn(":neoforge:build")
dependsOn(":neoforge:neoforge-api:build")
}
jar {
manifest {
attributes(
'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
'Implementation-Title': 'LuckPerms',
'Implementation-Vendor': 'LuckPerms',
'Implementation-Version': project.ext.fullVersion,
'Specification-Title': 'luckperms',
'Specification-Vendor': 'LuckPerms',
'Specification-Version': '1'
)
}
}
processResources {
filesMatching('META-INF/neoforge.mods.toml') {
expand 'version': project.ext.fullVersion
}
}
shadowJar {
archiveFileName = "LuckPerms-NeoForge-${project.ext.fullVersion}.jar"
configurations = [shade]
from {
project(':neoforge').tasks.shadowJar.archiveFile
}
dependencies {
include(dependency('net.luckperms:.*'))
include(dependency('me.lucko.luckperms:.*'))
}
}
artifacts {
archives shadowJar
}

View File

@ -0,0 +1,74 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge.loader;
import me.lucko.luckperms.common.loader.JarInJarClassLoader;
import me.lucko.luckperms.common.loader.LoaderBootstrap;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
import net.neoforged.fml.loading.FMLEnvironment;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.function.Supplier;
@Mod(value = "luckperms")
public class NeoForgeLoaderPlugin implements Supplier<ModContainer> {
private static final Logger LOGGER = LogManager.getLogger("luckperms");
private static final String JAR_NAME = "luckperms-neoforge.jarinjar";
private static final String BOOTSTRAP_CLASS = "me.lucko.luckperms.neoforge.LPNeoForgeBootstrap";
private final ModContainer container;
private JarInJarClassLoader loader;
private LoaderBootstrap plugin;
public NeoForgeLoaderPlugin(final ModContainer modContainer, final IEventBus modBus) {
this.container = modContainer;
if (FMLEnvironment.dist.isClient()) {
LOGGER.info("Skipping LuckPerms init (not supported on the client!)");
return;
}
this.loader = new JarInJarClassLoader(getClass().getClassLoader(), JAR_NAME);
modBus.addListener(this::onCommonSetup);
}
@Override
public ModContainer get() {
return this.container;
}
public void onCommonSetup(FMLCommonSetupEvent event) {
this.plugin = this.loader.instantiatePlugin(BOOTSTRAP_CLASS, Supplier.class, this);
this.plugin.onLoad();
}
}

View File

@ -0,0 +1,16 @@
modLoader="javafml"
loaderVersion="[1,)"
license="MIT"
issueTrackerURL="https://github.com/LuckPerms/LuckPerms/issues"
showAsResourcePack=false
[[mods]]
modId="luckperms"
version="${version}"
displayName="LuckPerms"
displayURL="https://luckperms.net/"
logoFile="luckperms.png"
credits="Luck"
authors="Luck"
description="A permissions plugin for Minecraft servers."
displayTest="IGNORE_ALL_VERSION"

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -0,0 +1,6 @@
{
"pack": {
"description": "LuckPerms resources",
"pack_format": 9
}
}

View File

@ -0,0 +1,16 @@
plugins {
alias(libs.plugins.moddevgradle)
}
sourceCompatibility = 17
targetCompatibility = 21
neoForge {
version = project.neoForgeVersion
validateAccessTransformers = true
}
dependencies {
implementation project(':api')
}

View File

@ -0,0 +1,83 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge.capabilities;
import net.luckperms.api.query.QueryOptions;
import net.luckperms.api.util.Tristate;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.neoforged.neoforge.capabilities.EntityCapability;
/**
* A NeoForge {@link EntityCapability} that attaches LuckPerms functionality onto {@link ServerPlayer}s.
*/
public interface UserCapability {
/**
* The identifier used for the capability
*/
ResourceLocation IDENTIFIER = ResourceLocation.fromNamespaceAndPath("luckperms", "user");
/**
* The capability instance.
*/
EntityCapability<UserCapability, Void> CAPABILITY = EntityCapability.createVoid(IDENTIFIER, UserCapability.class);
/**
* Checks for a permission.
*
* @param permission the permission
* @return the result
*/
default boolean hasPermission(String permission) {
return checkPermission(permission).asBoolean();
}
/**
* Runs a permission check.
*
* @param permission the permission
* @return the result
*/
Tristate checkPermission(String permission);
/**
* Runs a permission check.
*
* @param permission the permission
* @param queryOptions the query options
* @return the result
*/
Tristate checkPermission(String permission, QueryOptions queryOptions);
/**
* Gets the user's currently query options.
*
* @return the current query options for the user
*/
QueryOptions getQueryOptions();
}

View File

@ -0,0 +1,287 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge;
import com.mojang.authlib.GameProfile;
import me.lucko.luckperms.common.loader.LoaderBootstrap;
import me.lucko.luckperms.common.plugin.bootstrap.BootstrappedWithLoader;
import me.lucko.luckperms.common.plugin.bootstrap.LuckPermsBootstrap;
import me.lucko.luckperms.common.plugin.classpath.ClassPathAppender;
import me.lucko.luckperms.common.plugin.classpath.JarInJarClassPathAppender;
import me.lucko.luckperms.common.plugin.logging.Log4jPluginLogger;
import me.lucko.luckperms.common.plugin.logging.PluginLogger;
import me.lucko.luckperms.common.plugin.scheduler.SchedulerAdapter;
import me.lucko.luckperms.neoforge.util.NeoForgeEventBusFacade;
import net.luckperms.api.platform.Platform;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.players.PlayerList;
import net.neoforged.bus.api.EventPriority;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.ModList;
import net.neoforged.fml.loading.FMLPaths;
import net.neoforged.neoforge.event.server.ServerAboutToStartEvent;
import net.neoforged.neoforge.event.server.ServerStoppingEvent;
import net.neoforged.neoforgespi.language.IModInfo;
import org.apache.logging.log4j.LogManager;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.function.Supplier;
/**
* Bootstrap plugin for LuckPerms running on Forge.
*/
public final class LPNeoForgeBootstrap implements LuckPermsBootstrap, LoaderBootstrap, BootstrappedWithLoader {
public static final String ID = "luckperms";
/**
* The plugin loader
*/
private final Supplier<ModContainer> loader;
/**
* The plugin logger
*/
private final PluginLogger logger;
/**
* A scheduler adapter for the platform
*/
private final SchedulerAdapter schedulerAdapter;
/**
* The plugin class path appender
*/
private final ClassPathAppender classPathAppender;
/**
* A facade for the forge event bus, compatible with LP's jar-in-jar packaging
*/
private final NeoForgeEventBusFacade forgeEventBus;
/**
* The plugin instance
*/
private final LPNeoForgePlugin plugin;
/**
* The time when the plugin was enabled
*/
private Instant startTime;
// load/enable latches
private final CountDownLatch loadLatch = new CountDownLatch(1);
private final CountDownLatch enableLatch = new CountDownLatch(1);
/**
* The Minecraft server instance
*/
private MinecraftServer server;
public LPNeoForgeBootstrap(Supplier<ModContainer> loader) {
this.loader = loader;
this.logger = new Log4jPluginLogger(LogManager.getLogger(LPNeoForgeBootstrap.ID));
this.schedulerAdapter = new NeoForgeSchedulerAdapter(this);
this.classPathAppender = new JarInJarClassPathAppender(getClass().getClassLoader());
this.forgeEventBus = new NeoForgeEventBusFacade();
this.plugin = new LPNeoForgePlugin(this);
}
// provide adapters
@Override
public Object getLoader() {
return this.loader;
}
@Override
public PluginLogger getPluginLogger() {
return this.logger;
}
@Override
public SchedulerAdapter getScheduler() {
return this.schedulerAdapter;
}
@Override
public ClassPathAppender getClassPathAppender() {
return this.classPathAppender;
}
public void registerListeners(Object target) {
this.forgeEventBus.register(target);
}
// lifecycle
@Override
public void onLoad() { // called by the loader on FMLCommonSetupEvent
this.startTime = Instant.now();
try {
this.plugin.load();
} finally {
this.loadLatch.countDown();
}
this.forgeEventBus.register(this);
this.plugin.registerEarlyListeners();
}
@SubscribeEvent(priority = EventPriority.HIGHEST)
public void onServerAboutToStart(ServerAboutToStartEvent event) {
this.server = event.getServer();
try {
this.plugin.enable();
} finally {
this.enableLatch.countDown();
}
}
@SubscribeEvent(priority = EventPriority.LOWEST)
public void onServerStopping(ServerStoppingEvent event) {
this.plugin.disable();
this.forgeEventBus.unregisterAll();
this.server = null;
}
@Override
public CountDownLatch getLoadLatch() {
return this.loadLatch;
}
@Override
public CountDownLatch getEnableLatch() {
return this.enableLatch;
}
// MinecraftServer singleton getter
public Optional<MinecraftServer> getServer() {
return Optional.ofNullable(this.server);
}
// provide information about the plugin
@Override
public String getVersion() {
return "@version@";
}
@Override
public Instant getStartupTime() {
return this.startTime;
}
// provide information about the platform
@Override
public Platform.Type getType() {
return Platform.Type.NEOFORGE;
}
@Override
public String getServerBrand() {
return ModList.get().getModContainerById("neoforge")
.map(ModContainer::getModInfo)
.map(IModInfo::getDisplayName)
.orElse("null");
}
@Override
public String getServerVersion() {
String forgeVersion = ModList.get().getModContainerById("neoforge")
.map(ModContainer::getModInfo)
.map(IModInfo::getVersion)
.map(ArtifactVersion::toString)
.orElse("null");
return getServer().map(MinecraftServer::getServerVersion).orElse("null") + "-" + forgeVersion;
}
@Override
public Path getDataDirectory() {
return FMLPaths.CONFIGDIR.get().resolve(LPNeoForgeBootstrap.ID).toAbsolutePath();
}
@Override
public Optional<ServerPlayer> getPlayer(UUID uniqueId) {
return getServer().map(MinecraftServer::getPlayerList).map(playerList -> playerList.getPlayer(uniqueId));
}
@Override
public Optional<UUID> lookupUniqueId(String username) {
return getServer().map(MinecraftServer::getProfileCache).flatMap(profileCache -> profileCache.get(username)).map(GameProfile::getId);
}
@Override
public Optional<String> lookupUsername(UUID uniqueId) {
return getServer().map(MinecraftServer::getProfileCache).flatMap(profileCache -> profileCache.get(uniqueId)).map(GameProfile::getName);
}
@Override
public int getPlayerCount() {
return getServer().map(MinecraftServer::getPlayerCount).orElse(0);
}
@Override
public Collection<String> getPlayerList() {
return getServer().map(MinecraftServer::getPlayerList).map(PlayerList::getPlayers).map(players -> {
List<String> list = new ArrayList<>(players.size());
for (ServerPlayer player : players) {
list.add(player.getGameProfile().getName());
}
return list;
}).orElse(Collections.emptyList());
}
@Override
public Collection<UUID> getOnlinePlayers() {
return getServer().map(MinecraftServer::getPlayerList).map(PlayerList::getPlayers).map(players -> {
List<UUID> list = new ArrayList<>(players.size());
for (ServerPlayer player : players) {
list.add(player.getGameProfile().getId());
}
return list;
}).orElse(Collections.emptyList());
}
@Override
public boolean isPlayerOnline(UUID uniqueId) {
return getServer().map(MinecraftServer::getPlayerList).map(playerList -> playerList.getPlayer(uniqueId)).isPresent();
}
}

View File

@ -0,0 +1,251 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge;
import me.lucko.luckperms.common.api.LuckPermsApiProvider;
import me.lucko.luckperms.common.calculator.CalculatorFactory;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.config.generic.adapter.ConfigurationAdapter;
import me.lucko.luckperms.common.dependencies.Dependency;
import me.lucko.luckperms.common.event.AbstractEventBus;
import me.lucko.luckperms.common.locale.TranslationManager;
import me.lucko.luckperms.common.messaging.MessagingFactory;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.model.manager.group.StandardGroupManager;
import me.lucko.luckperms.common.model.manager.track.StandardTrackManager;
import me.lucko.luckperms.common.model.manager.user.StandardUserManager;
import me.lucko.luckperms.common.plugin.AbstractLuckPermsPlugin;
import me.lucko.luckperms.common.sender.DummyConsoleSender;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.neoforge.calculator.NeoForgeCalculatorFactory;
import me.lucko.luckperms.neoforge.capabilities.UserCapabilityListener;
import me.lucko.luckperms.neoforge.context.NeoForgeContextManager;
import me.lucko.luckperms.neoforge.context.NeoForgePlayerCalculator;
import me.lucko.luckperms.neoforge.listeners.NeoForgeAutoOpListener;
import me.lucko.luckperms.neoforge.listeners.NeoForgeCommandListUpdater;
import me.lucko.luckperms.neoforge.listeners.NeoForgeConnectionListener;
import me.lucko.luckperms.neoforge.listeners.NeoForgePlatformListener;
import me.lucko.luckperms.neoforge.messaging.NeoForgeMessagingFactory;
import me.lucko.luckperms.neoforge.messaging.PluginMessageMessenger;
import me.lucko.luckperms.neoforge.service.NeoForgePermissionHandlerListener;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.query.QueryOptions;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.players.PlayerList;
import net.neoforged.fml.ModContainer;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
/**
* LuckPerms implementation for Forge.
*/
public class LPNeoForgePlugin extends AbstractLuckPermsPlugin {
private final LPNeoForgeBootstrap bootstrap;
private NeoForgeSenderFactory senderFactory;
private NeoForgeConnectionListener connectionListener;
private NeoForgeCommandExecutor commandManager;
private StandardUserManager userManager;
private StandardGroupManager groupManager;
private StandardTrackManager trackManager;
private NeoForgeContextManager contextManager;
public LPNeoForgePlugin(LPNeoForgeBootstrap bootstrap) {
this.bootstrap = bootstrap;
}
@Override
public LPNeoForgeBootstrap getBootstrap() {
return this.bootstrap;
}
protected void registerEarlyListeners() {
this.connectionListener = new NeoForgeConnectionListener(this);
this.bootstrap.registerListeners(this.connectionListener);
NeoForgePlatformListener platformListener = new NeoForgePlatformListener(this);
this.bootstrap.registerListeners(platformListener);
UserCapabilityListener userCapabilityListener = new UserCapabilityListener();
this.bootstrap.registerListeners(userCapabilityListener);
NeoForgePermissionHandlerListener permissionHandlerListener = new NeoForgePermissionHandlerListener(this);
this.bootstrap.registerListeners(permissionHandlerListener);
this.commandManager = new NeoForgeCommandExecutor(this);
this.bootstrap.registerListeners(this.commandManager);
PluginMessageMessenger.registerChannel();
}
@Override
protected void setupSenderFactory() {
this.senderFactory = new NeoForgeSenderFactory(this);
}
@Override
protected Set<Dependency> getGlobalDependencies() {
Set<Dependency> dependencies = super.getGlobalDependencies();
dependencies.add(Dependency.CONFIGURATE_CORE);
dependencies.add(Dependency.CONFIGURATE_HOCON);
dependencies.add(Dependency.HOCON_CONFIG);
return dependencies;
}
@Override
protected ConfigurationAdapter provideConfigurationAdapter() {
return new NeoForgeConfigAdapter(this, resolveConfig("luckperms.conf"));
}
@Override
protected void registerPlatformListeners() {
// Too late for Forge, registered in #registerEarlyListeners
}
@Override
protected MessagingFactory<?> provideMessagingFactory() {
return new NeoForgeMessagingFactory(this);
}
@Override
protected void registerCommands() {
// Too late for Forge, registered in #registerEarlyListeners
}
@Override
protected void setupManagers() {
this.userManager = new StandardUserManager(this);
this.groupManager = new StandardGroupManager(this);
this.trackManager = new StandardTrackManager(this);
}
@Override
protected CalculatorFactory provideCalculatorFactory() {
return new NeoForgeCalculatorFactory(this);
}
@Override
protected void setupContextManager() {
this.contextManager = new NeoForgeContextManager(this);
NeoForgePlayerCalculator playerCalculator = new NeoForgePlayerCalculator(this, getConfiguration().get(ConfigKeys.DISABLED_CONTEXTS));
this.bootstrap.registerListeners(playerCalculator);
this.contextManager.registerCalculator(playerCalculator);
}
@Override
protected void setupPlatformHooks() {
}
@Override
protected AbstractEventBus<ModContainer> provideEventBus(LuckPermsApiProvider provider) {
return new NeoForgeEventBus(this, provider);
}
@Override
protected void registerApiOnPlatform(LuckPerms api) {
}
@Override
protected void performFinalSetup() {
// register autoop listener
if (getConfiguration().get(ConfigKeys.AUTO_OP)) {
getApiProvider().getEventBus().subscribe(new NeoForgeAutoOpListener(this));
}
// register forge command list updater
if (getConfiguration().get(ConfigKeys.UPDATE_CLIENT_COMMAND_LIST)) {
getApiProvider().getEventBus().subscribe(new NeoForgeCommandListUpdater(this));
}
}
@Override
public Optional<QueryOptions> getQueryOptionsForUser(User user) {
return this.bootstrap.getPlayer(user.getUniqueId()).map(player -> this.contextManager.getQueryOptions(player));
}
@Override
public Stream<Sender> getOnlineSenders() {
return Stream.concat(
Stream.of(getConsoleSender()),
this.bootstrap.getServer()
.map(MinecraftServer::getPlayerList)
.map(PlayerList::getPlayers)
.map(players -> players.stream().map(player -> this.senderFactory.wrap(player.createCommandSourceStack()))).orElseGet(Stream::empty)
);
}
@Override
public Sender getConsoleSender() {
return this.bootstrap.getServer()
.map(server -> this.senderFactory.wrap(server.createCommandSourceStack()))
.orElseGet(() -> new DummyConsoleSender(this) {
@Override
public void sendMessage(Component message) {
LPNeoForgePlugin.this.bootstrap.getPluginLogger().info(PlainTextComponentSerializer.plainText().serialize(TranslationManager.render(message)));
}
});
}
public NeoForgeSenderFactory getSenderFactory() {
return this.senderFactory;
}
@Override
public NeoForgeConnectionListener getConnectionListener() {
return this.connectionListener;
}
@Override
public NeoForgeCommandExecutor getCommandManager() {
return this.commandManager;
}
@Override
public StandardUserManager getUserManager() {
return this.userManager;
}
@Override
public StandardGroupManager getGroupManager() {
return this.groupManager;
}
@Override
public StandardTrackManager getTrackManager() {
return this.trackManager;
}
@Override
public NeoForgeContextManager getContextManager() {
return this.contextManager;
}
}

View File

@ -0,0 +1,108 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.tree.ArgumentCommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import me.lucko.luckperms.common.command.BrigadierCommandExecutor;
import me.lucko.luckperms.common.sender.Sender;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.server.level.ServerPlayer;
import java.util.List;
import java.util.ListIterator;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.neoforge.event.RegisterCommandsEvent;
public class NeoForgeCommandExecutor extends BrigadierCommandExecutor<CommandSourceStack> {
private final LPNeoForgePlugin plugin;
public NeoForgeCommandExecutor(LPNeoForgePlugin plugin) {
super(plugin);
this.plugin = plugin;
}
@SubscribeEvent
public void onRegisterCommands(RegisterCommandsEvent event) {
for (String alias : COMMAND_ALIASES) {
LiteralCommandNode<CommandSourceStack> command = Commands.literal(alias).executes(this).build();
ArgumentCommandNode<CommandSourceStack, String> argument = Commands.argument("args", StringArgumentType.greedyString())
.suggests(this)
.executes(this)
.build();
command.addChild(argument);
event.getDispatcher().getRoot().addChild(command);
}
}
@Override
public Sender getSender(CommandSourceStack source) {
return this.plugin.getSenderFactory().wrap(source);
}
@Override
public List<String> resolveSelectors(CommandSourceStack source, List<String> args) {
// usage of @ selectors requires at least level 2 permission
CommandSourceStack atAllowedSource = source.hasPermission(2) ? source : source.withPermission(2);
for (ListIterator<String> it = args.listIterator(); it.hasNext(); ) {
String arg = it.next();
if (arg.isEmpty() || arg.charAt(0) != '@') {
continue;
}
List<ServerPlayer> matchedPlayers;
try {
matchedPlayers = EntityArgument.entities().parse(new StringReader(arg)).findPlayers(atAllowedSource);
} catch (CommandSyntaxException e) {
this.plugin.getLogger().warn("Error parsing selector '" + arg + "' for " + source + " executing " + args, e);
continue;
}
if (matchedPlayers.isEmpty()) {
continue;
}
if (matchedPlayers.size() > 1) {
this.plugin.getLogger().warn("Error parsing selector '" + arg + "' for " + source + " executing " + args +
": ambiguous result (more than one player matched) - " + matchedPlayers);
continue;
}
ServerPlayer player = matchedPlayers.get(0);
it.set(player.getStringUUID());
}
return args;
}
}

View File

@ -0,0 +1,46 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge;
import me.lucko.luckperms.common.config.generic.adapter.ConfigurateConfigAdapter;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.hocon.HoconConfigurationLoader;
import ninja.leaping.configurate.loader.ConfigurationLoader;
import java.nio.file.Path;
public class NeoForgeConfigAdapter extends ConfigurateConfigAdapter {
public NeoForgeConfigAdapter(LuckPermsPlugin plugin, Path path) {
super(plugin, path);
}
@Override
protected ConfigurationLoader<? extends ConfigurationNode> createLoader(Path path) {
return HoconConfigurationLoader.builder().setPath(path).build();
}
}

View File

@ -0,0 +1,47 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge;
import me.lucko.luckperms.common.api.LuckPermsApiProvider;
import me.lucko.luckperms.common.event.AbstractEventBus;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import net.neoforged.fml.ModContainer;
public class NeoForgeEventBus extends AbstractEventBus<ModContainer> {
public NeoForgeEventBus(LuckPermsPlugin plugin, LuckPermsApiProvider apiProvider) {
super(plugin, apiProvider);
}
@Override
protected ModContainer checkPlugin(Object modContainer) throws IllegalArgumentException {
if (modContainer instanceof ModContainer container) {
return container;
}
throw new IllegalArgumentException("Object " + modContainer + " (" + modContainer.getClass().getName() + ") is not a ModContainer.");
}
}

View File

@ -0,0 +1,45 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge;
import me.lucko.luckperms.common.plugin.scheduler.AbstractJavaScheduler;
import java.util.concurrent.Executor;
public class NeoForgeSchedulerAdapter extends AbstractJavaScheduler {
private final Executor sync;
public NeoForgeSchedulerAdapter(LPNeoForgeBootstrap bootstrap) {
super(bootstrap);
this.sync = r -> bootstrap.getServer().orElseThrow(() -> new IllegalStateException("Server not ready")).executeBlocking(r);
}
@Override
public Executor sync() {
return this.sync;
}
}

View File

@ -0,0 +1,124 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge;
import com.mojang.brigadier.ParseResults;
import me.lucko.luckperms.common.cacheddata.result.TristateResult;
import me.lucko.luckperms.common.locale.TranslationManager;
import me.lucko.luckperms.common.query.QueryOptionsImpl;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.sender.SenderFactory;
import me.lucko.luckperms.common.verbose.VerboseCheckTarget;
import me.lucko.luckperms.common.verbose.event.CheckOrigin;
import me.lucko.luckperms.neoforge.capabilities.UserCapability;
import me.lucko.luckperms.neoforge.capabilities.UserCapabilityImpl;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.luckperms.api.util.Tristate;
import net.minecraft.commands.CommandSource;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.RegistryAccess;
import net.minecraft.network.chat.Component.Serializer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.rcon.RconConsoleSource;
import net.minecraft.world.entity.player.Player;
import java.util.Locale;
import java.util.UUID;
public class NeoForgeSenderFactory extends SenderFactory<LPNeoForgePlugin, CommandSourceStack> {
public NeoForgeSenderFactory(LPNeoForgePlugin plugin) {
super(plugin);
}
@Override
protected UUID getUniqueId(CommandSourceStack commandSource) {
if (commandSource.getEntity() instanceof Player) {
return commandSource.getEntity().getUUID();
}
return Sender.CONSOLE_UUID;
}
@Override
protected String getName(CommandSourceStack commandSource) {
if (commandSource.getEntity() instanceof Player) {
return commandSource.getTextName();
}
return Sender.CONSOLE_NAME;
}
@Override
protected void sendMessage(CommandSourceStack sender, Component message) {
Locale locale;
if (sender.getEntity() instanceof ServerPlayer) {
ServerPlayer player = (ServerPlayer) sender.getEntity();
UserCapabilityImpl user = UserCapabilityImpl.get(player);
locale = user.getLocale(player);
} else {
locale = null;
}
sender.sendSuccess(() -> toNativeText(TranslationManager.render(message, locale)), false);
}
@Override
protected Tristate getPermissionValue(CommandSourceStack commandSource, String node) {
if (commandSource.getEntity() instanceof ServerPlayer) {
ServerPlayer player = (ServerPlayer) commandSource.getEntity();
UserCapability user = UserCapabilityImpl.get(player);
return user.checkPermission(node);
}
VerboseCheckTarget target = VerboseCheckTarget.internal(commandSource.getTextName());
getPlugin().getVerboseHandler().offerPermissionCheckEvent(CheckOrigin.PLATFORM_API_HAS_PERMISSION, target, QueryOptionsImpl.DEFAULT_CONTEXTUAL, node, TristateResult.UNDEFINED);
getPlugin().getPermissionRegistry().offer(node);
return Tristate.UNDEFINED;
}
@Override
protected boolean hasPermission(CommandSourceStack commandSource, String node) {
return getPermissionValue(commandSource, node).asBoolean();
}
@Override
protected void performCommand(CommandSourceStack sender, String command) {
ParseResults<CommandSourceStack> results = sender.getServer().getCommands().getDispatcher().parse(command, sender);
sender.getServer().getCommands().performCommand(results, command);
}
@Override
protected boolean isConsole(CommandSourceStack sender) {
CommandSource output = sender.source;
return output == sender.getServer() || // Console
output.getClass() == RconConsoleSource.class || // Rcon
(output == CommandSource.NULL && sender.getTextName().equals("")); // Functions
}
public static net.minecraft.network.chat.Component toNativeText(Component component) {
return Serializer.fromJson(GsonComponentSerializer.gson().serializeToTree(component), RegistryAccess.EMPTY);
}
}

View File

@ -0,0 +1,77 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge.calculator;
import me.lucko.luckperms.common.cacheddata.CacheMetadata;
import me.lucko.luckperms.common.calculator.CalculatorFactory;
import me.lucko.luckperms.common.calculator.PermissionCalculator;
import me.lucko.luckperms.common.calculator.processor.DirectProcessor;
import me.lucko.luckperms.common.calculator.processor.PermissionProcessor;
import me.lucko.luckperms.common.calculator.processor.RegexProcessor;
import me.lucko.luckperms.common.calculator.processor.SpongeWildcardProcessor;
import me.lucko.luckperms.common.calculator.processor.WildcardProcessor;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.neoforge.LPNeoForgePlugin;
import me.lucko.luckperms.neoforge.context.NeoForgeContextManager;
import net.luckperms.api.query.QueryOptions;
import java.util.ArrayList;
import java.util.List;
public class NeoForgeCalculatorFactory implements CalculatorFactory {
private final LPNeoForgePlugin plugin;
public NeoForgeCalculatorFactory(LPNeoForgePlugin plugin) {
this.plugin = plugin;
}
@Override
public PermissionCalculator build(QueryOptions queryOptions, CacheMetadata metadata) {
List<PermissionProcessor> processors = new ArrayList<>(5);
processors.add(new DirectProcessor());
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.APPLYING_WILDCARDS_SPONGE)) {
processors.add(new SpongeWildcardProcessor());
}
boolean integratedOwner = queryOptions.option(NeoForgeContextManager.INTEGRATED_SERVER_OWNER).orElse(false);
if (integratedOwner && this.plugin.getConfiguration().get(ConfigKeys.INTEGRATED_SERVER_OWNER_BYPASSES_CHECKS)) {
processors.add(ServerOwnerProcessor.INSTANCE);
}
return new PermissionCalculator(this.plugin, metadata, processors);
}
}

View File

@ -0,0 +1,51 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge.calculator;
import me.lucko.luckperms.common.cacheddata.result.TristateResult;
import me.lucko.luckperms.common.calculator.processor.AbstractPermissionProcessor;
import me.lucko.luckperms.common.calculator.processor.PermissionProcessor;
import net.luckperms.api.util.Tristate;
/**
* Permission processor which is added to the owner of an Integrated server to
* simply return true if no other processors match.
*/
public class ServerOwnerProcessor extends AbstractPermissionProcessor implements PermissionProcessor {
private static final TristateResult TRUE_RESULT = new TristateResult.Factory(ServerOwnerProcessor.class).result(Tristate.TRUE);
public static final ServerOwnerProcessor INSTANCE = new ServerOwnerProcessor();
private ServerOwnerProcessor() {
}
@Override
public TristateResult hasPermission(String permission) {
return TRUE_RESULT;
}
}

View File

@ -0,0 +1,163 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge.capabilities;
import java.util.Optional;
import me.lucko.luckperms.common.cacheddata.type.PermissionCache;
import me.lucko.luckperms.common.context.manager.QueryOptionsCache;
import me.lucko.luckperms.common.locale.TranslationManager;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.verbose.event.CheckOrigin;
import me.lucko.luckperms.neoforge.context.NeoForgeContextManager;
import net.luckperms.api.query.QueryOptions;
import net.luckperms.api.util.Tristate;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Locale;
public class UserCapabilityImpl implements UserCapability {
private static Optional<UserCapability> getCapability(Player player) {
return Optional.ofNullable(player.getCapability(CAPABILITY));
}
/**
* Gets a {@link UserCapability} for a given {@link ServerPlayer}.
*
* @param player the player
* @return the capability
*/
public static @NotNull UserCapabilityImpl get(@NotNull Player player) {
return (UserCapabilityImpl) getCapability(player).orElseThrow(() -> new IllegalStateException("Capability missing for " + player.getUUID()));
}
/**
* Gets a {@link UserCapability} for a given {@link ServerPlayer}.
*
* @param player the player
* @return the capability, or null
*/
public static @Nullable UserCapabilityImpl getNullable(@NotNull Player player) {
return (UserCapabilityImpl) getCapability(player).orElse(null);
}
private boolean initialised = false;
private boolean invalidated = false;
private User user;
private QueryOptionsCache<ServerPlayer> queryOptionsCache;
private String language;
private Locale locale;
public UserCapabilityImpl() {
}
public void initialise(UserCapabilityImpl previous) {
this.user = previous.user;
this.queryOptionsCache = previous.queryOptionsCache;
this.language = previous.language;
this.locale = previous.locale;
this.initialised = true;
}
public void initialise(User user, ServerPlayer player, NeoForgeContextManager contextManager) {
this.user = user;
this.queryOptionsCache = new QueryOptionsCache<>(player, contextManager);
this.initialised = true;
}
private void assertInitialised() {
if (!this.initialised) {
throw new IllegalStateException("Capability has not been initialised");
}
if (this.invalidated) {
throw new IllegalStateException("Capability has been invalidated");
}
}
public void invalidate() {
this.invalidated = false;
this.user = null;
this.queryOptionsCache = null;
this.language = null;
this.locale = null;
}
@Override
public Tristate checkPermission(String permission) {
assertInitialised();
if (permission == null) {
throw new NullPointerException("permission");
}
return checkPermission(permission, this.queryOptionsCache.getQueryOptions());
}
@Override
public Tristate checkPermission(String permission, QueryOptions queryOptions) {
assertInitialised();
if (permission == null) {
throw new NullPointerException("permission");
}
if (queryOptions == null) {
throw new NullPointerException("queryOptions");
}
PermissionCache cache = this.user.getCachedData().getPermissionData(queryOptions);
return cache.checkPermission(permission, CheckOrigin.PLATFORM_API_HAS_PERMISSION).result();
}
public User getUser() {
assertInitialised();
return this.user;
}
@Override
public QueryOptions getQueryOptions() {
return getQueryOptionsCache().getQueryOptions();
}
public QueryOptionsCache<ServerPlayer> getQueryOptionsCache() {
assertInitialised();
return this.queryOptionsCache;
}
public Locale getLocale(ServerPlayer player) {
if (this.language == null || !this.language.equals(player.getLanguage())) {
this.language = player.getLanguage();
this.locale = TranslationManager.parseLocale(this.language);
}
return this.locale;
}
}

View File

@ -0,0 +1,69 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge.capabilities;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.player.Player;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent;
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
public class UserCapabilityListener {
@SubscribeEvent
public void onRegisterCapabilities(RegisterCapabilitiesEvent event) {
event.registerEntity(
UserCapability.CAPABILITY,
EntityType.PLAYER,
(player, ctx) -> {
if (!(player instanceof ServerPlayer)) {
// Don't attach to LocalPlayer
return null;
}
return new UserCapabilityImpl();
}
);
}
@SubscribeEvent
public void onPlayerClone(PlayerEvent.Clone event) {
Player previousPlayer = event.getOriginal();
Player currentPlayer = event.getEntity();
try {
UserCapabilityImpl previous = UserCapabilityImpl.get(previousPlayer);
UserCapabilityImpl current = UserCapabilityImpl.get(currentPlayer);
current.initialise(previous);
previous.invalidate();
current.getQueryOptionsCache().invalidate();
} catch (IllegalStateException e) {
// continue on if we cannot copy original data
}
}
}

View File

@ -0,0 +1,79 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge.context;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.manager.ContextManager;
import me.lucko.luckperms.common.context.manager.QueryOptionsCache;
import me.lucko.luckperms.neoforge.LPNeoForgePlugin;
import me.lucko.luckperms.neoforge.capabilities.UserCapabilityImpl;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.query.OptionKey;
import net.luckperms.api.query.QueryOptions;
import net.minecraft.server.level.ServerPlayer;
import java.util.UUID;
public class NeoForgeContextManager extends ContextManager<ServerPlayer, ServerPlayer> {
public static final OptionKey<Boolean> INTEGRATED_SERVER_OWNER = OptionKey.of("integrated_server_owner", Boolean.class);
public NeoForgeContextManager(LPNeoForgePlugin plugin) {
super(plugin, ServerPlayer.class, ServerPlayer.class);
}
@Override
public UUID getUniqueId(ServerPlayer player) {
return player.getUUID();
}
@Override
public QueryOptionsCache<ServerPlayer> getCacheFor(ServerPlayer subject) {
if (subject == null) {
throw new NullPointerException("subject");
}
return UserCapabilityImpl.get(subject).getQueryOptionsCache();
}
@Override
public QueryOptions formQueryOptions(ServerPlayer subject, ImmutableContextSet contextSet) {
QueryOptions.Builder builder = this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder();
if (subject.getServer() != null && subject.getServer().isSingleplayerOwner(subject.getGameProfile())) {
builder.option(INTEGRATED_SERVER_OWNER, true);
}
return builder.context(contextSet).build();
}
@Override
public void invalidateCache(ServerPlayer subject) {
UserCapabilityImpl capability = UserCapabilityImpl.getNullable(subject);
if (capability != null) {
capability.getQueryOptionsCache().invalidate();
}
}
}

View File

@ -0,0 +1,147 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge.context;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.ImmutableContextSetImpl;
import me.lucko.luckperms.neoforge.LPNeoForgePlugin;
import net.luckperms.api.context.Context;
import net.luckperms.api.context.ContextCalculator;
import net.luckperms.api.context.ContextConsumer;
import net.luckperms.api.context.ContextSet;
import net.luckperms.api.context.DefaultContextKeys;
import net.luckperms.api.context.ImmutableContextSet;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.storage.ServerLevelData;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Set;
public class NeoForgePlayerCalculator implements ContextCalculator<ServerPlayer> {
/**
* GameType.NOT_SET(-1, "") was removed in 1.17
*/
private static final int GAME_MODE_NOT_SET = -1;
private final LPNeoForgePlugin plugin;
private final boolean gamemode;
private final boolean world;
private final boolean dimensionType;
public NeoForgePlayerCalculator(LPNeoForgePlugin plugin, Set<String> disabled) {
this.plugin = plugin;
this.gamemode = !disabled.contains(DefaultContextKeys.GAMEMODE_KEY);
this.world = !disabled.contains(DefaultContextKeys.WORLD_KEY);
this.dimensionType = !disabled.contains(DefaultContextKeys.DIMENSION_TYPE_KEY);
}
@Override
public void calculate(@NonNull ServerPlayer target, @NonNull ContextConsumer consumer) {
ServerLevel level = target.serverLevel();
if (this.dimensionType) {
consumer.accept(DefaultContextKeys.DIMENSION_TYPE_KEY, getContextKey(level.dimension().location()));
}
ServerLevelData levelData = (ServerLevelData) level.getLevelData();
if (this.world) {
this.plugin.getConfiguration().get(ConfigKeys.WORLD_REWRITES).rewriteAndSubmit(levelData.getLevelName(), consumer);
}
GameType gameMode = target.gameMode.getGameModeForPlayer();
if (this.gamemode && gameMode.getId() != GAME_MODE_NOT_SET) {
consumer.accept(DefaultContextKeys.GAMEMODE_KEY, gameMode.getName());
}
}
@Override
public @NonNull ContextSet estimatePotentialContexts() {
ImmutableContextSet.Builder builder = new ImmutableContextSetImpl.BuilderImpl();
if (this.gamemode) {
for (GameType gameType : GameType.values()) {
if (gameType.getId() == GAME_MODE_NOT_SET) {
continue;
}
builder.add(DefaultContextKeys.GAMEMODE_KEY, gameType.getName());
}
}
MinecraftServer server = this.plugin.getBootstrap().getServer().orElse(null);
if (this.dimensionType && server != null) {
server.registryAccess().registry(Registries.DIMENSION_TYPE).ifPresent(registry -> {
for (ResourceLocation resourceLocation : registry.keySet()) {
builder.add(DefaultContextKeys.DIMENSION_TYPE_KEY, getContextKey(resourceLocation));
}
});
}
if (this.world && server != null) {
for (ServerLevel level : server.getAllLevels()) {
ServerLevelData levelData = (ServerLevelData) level.getLevelData();
if (Context.isValidValue(levelData.getLevelName())) {
builder.add(DefaultContextKeys.WORLD_KEY, levelData.getLevelName());
}
}
}
return builder.build();
}
private static String getContextKey(ResourceLocation key) {
if (key.getNamespace().equals("minecraft")) {
return key.getPath();
}
return key.toString();
}
@SubscribeEvent
public void onPlayerChangedDimension(PlayerEvent.PlayerChangedDimensionEvent event) {
if (!(this.world || this.dimensionType)) {
return;
}
this.plugin.getContextManager().signalContextUpdate((ServerPlayer) event.getEntity());
}
@SubscribeEvent
public void onPlayerChangeGameMode(PlayerEvent.PlayerChangeGameModeEvent event) {
if (!this.gamemode || event.getNewGameMode().getId() == GAME_MODE_NOT_SET) {
return;
}
this.plugin.getContextManager().signalContextUpdate((ServerPlayer) event.getEntity());
}
}

View File

@ -0,0 +1,97 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge.listeners;
import me.lucko.luckperms.common.api.implementation.ApiUser;
import me.lucko.luckperms.common.event.LuckPermsEventListener;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.neoforge.LPNeoForgePlugin;
import net.luckperms.api.event.EventBus;
import net.luckperms.api.event.context.ContextUpdateEvent;
import net.luckperms.api.event.user.UserDataRecalculateEvent;
import net.luckperms.api.query.QueryOptions;
import net.minecraft.server.level.ServerPlayer;
import java.util.Map;
public class NeoForgeAutoOpListener implements LuckPermsEventListener {
private static final String NODE = "luckperms.autoop";
private final LPNeoForgePlugin plugin;
public NeoForgeAutoOpListener(LPNeoForgePlugin plugin) {
this.plugin = plugin;
}
@Override
public void bind(EventBus bus) {
bus.subscribe(ContextUpdateEvent.class, this::onContextUpdate);
bus.subscribe(UserDataRecalculateEvent.class, this::onUserDataRecalculate);
}
private void onContextUpdate(ContextUpdateEvent event) {
event.getSubject(ServerPlayer.class).ifPresent(player -> refreshAutoOp(player, true));
}
private void onUserDataRecalculate(UserDataRecalculateEvent event) {
User user = ApiUser.cast(event.getUser());
this.plugin.getBootstrap().getPlayer(user.getUniqueId()).ifPresent(player -> refreshAutoOp(player, false));
}
private void refreshAutoOp(ServerPlayer player, boolean callerIsSync) {
if (!callerIsSync && !this.plugin.getBootstrap().getServer().isPresent()) {
return;
}
User user = this.plugin.getUserManager().getIfLoaded(player.getUUID());
boolean value;
if (user != null) {
QueryOptions queryOptions = this.plugin.getContextManager().getQueryOptions(player);
Map<String, Boolean> permData = user.getCachedData().getPermissionData(queryOptions).getPermissionMap();
value = permData.getOrDefault(NODE, false);
} else {
value = false;
}
if (callerIsSync) {
setOp(player, value);
} else {
this.plugin.getBootstrap().getScheduler().executeSync(() -> setOp(player, value));
}
}
private void setOp(ServerPlayer player, boolean value) {
this.plugin.getBootstrap().getServer().ifPresent(server -> {
if (value) {
server.getPlayerList().op(player.getGameProfile());
} else {
server.getPlayerList().deop(player.getGameProfile());
}
});
}
}

View File

@ -0,0 +1,119 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge.listeners;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.common.api.implementation.ApiGroup;
import me.lucko.luckperms.common.cache.BufferedRequest;
import me.lucko.luckperms.common.event.LuckPermsEventListener;
import me.lucko.luckperms.common.util.CaffeineFactory;
import me.lucko.luckperms.neoforge.LPNeoForgePlugin;
import net.luckperms.api.event.EventBus;
import net.luckperms.api.event.context.ContextUpdateEvent;
import net.luckperms.api.event.group.GroupDataRecalculateEvent;
import net.luckperms.api.event.user.UserDataRecalculateEvent;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* Calls {@link net.minecraft.server.players.PlayerList#sendPlayerPermissionLevel(ServerPlayer)} when a players permissions change.
*/
public class NeoForgeCommandListUpdater implements LuckPermsEventListener {
private final LPNeoForgePlugin plugin;
private final LoadingCache<UUID, SendBuffer> sendingBuffers = CaffeineFactory.newBuilder()
.expireAfterAccess(10, TimeUnit.SECONDS)
.build(SendBuffer::new);
public NeoForgeCommandListUpdater(LPNeoForgePlugin plugin) {
this.plugin = plugin;
}
@Override
public void bind(EventBus bus) {
bus.subscribe(UserDataRecalculateEvent.class, this::onUserDataRecalculate);
bus.subscribe(GroupDataRecalculateEvent.class, this::onGroupDataRecalculate);
bus.subscribe(ContextUpdateEvent.class, this::onContextUpdate);
}
private void onUserDataRecalculate(UserDataRecalculateEvent e) {
requestUpdate(e.getUser().getUniqueId());
}
private void onGroupDataRecalculate(GroupDataRecalculateEvent e) {
plugin.getUserManager().getAll().values().forEach(user -> {
if (user.resolveInheritanceTree(user.getQueryOptions()).contains(ApiGroup.cast(e.getGroup()))) {
requestUpdate(user.getUniqueId());
}
});
}
private void onContextUpdate(ContextUpdateEvent e) {
e.getSubject(ServerPlayer.class).ifPresent(p -> requestUpdate(p.getUUID()));
}
private void requestUpdate(UUID uniqueId) {
if (!this.plugin.getBootstrap().isPlayerOnline(uniqueId)) {
return;
}
// Buffer the request to send a commands update.
SendBuffer sendBuffer = this.sendingBuffers.get(uniqueId);
if (sendBuffer != null) {
sendBuffer.request();
}
}
// Called when the buffer times out.
private void sendUpdate(UUID uniqueId) {
this.plugin.getBootstrap().getScheduler().sync().execute(() -> {
this.plugin.getBootstrap().getPlayer(uniqueId).ifPresent(player -> {
MinecraftServer server = player.getServer();
if (server != null) {
server.getPlayerList().sendPlayerPermissionLevel(player);
}
});
});
}
private final class SendBuffer extends BufferedRequest<Void> {
private final UUID uniqueId;
SendBuffer(UUID uniqueId) {
super(500, TimeUnit.MILLISECONDS, NeoForgeCommandListUpdater.this.plugin.getBootstrap().getScheduler());
this.uniqueId = uniqueId;
}
@Override
protected Void perform() {
sendUpdate(this.uniqueId);
return null;
}
}
}

View File

@ -0,0 +1,158 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge.listeners;
import com.mojang.authlib.GameProfile;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.locale.TranslationManager;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.plugin.util.AbstractConnectionListener;
import me.lucko.luckperms.neoforge.NeoForgeSenderFactory;
import me.lucko.luckperms.neoforge.LPNeoForgePlugin;
import me.lucko.luckperms.neoforge.capabilities.UserCapabilityImpl;
import me.lucko.luckperms.neoforge.util.AsyncConfigurationTask;
import net.kyori.adventure.text.Component;
import net.minecraft.network.Connection;
import net.minecraft.network.PacketListener;
import net.minecraft.network.protocol.login.ClientboundLoginDisconnectPacket;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ConfigurationTask;
import net.minecraft.server.network.ServerConfigurationPacketListenerImpl;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import net.neoforged.bus.api.EventPriority;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
import net.neoforged.neoforge.network.event.RegisterConfigurationTasksEvent;
public class NeoForgeConnectionListener extends AbstractConnectionListener {
private static final ConfigurationTask.Type USER_LOGIN_TASK_TYPE = new ConfigurationTask.Type("luckperms:user_login");
private final LPNeoForgePlugin plugin;
public NeoForgeConnectionListener(LPNeoForgePlugin plugin) {
super(plugin);
this.plugin = plugin;
}
@SubscribeEvent
public void onGatherLoginConfigurationTasks(RegisterConfigurationTasksEvent event) {
PacketListener packetListener = event.getListener();
if (!(packetListener instanceof ServerConfigurationPacketListenerImpl)) {
return;
}
GameProfile gameProfile = ((ServerConfigurationPacketListenerImpl) packetListener).getOwner();
if (gameProfile == null) {
return;
}
String username = gameProfile.getName();
UUID uniqueId = gameProfile.getId();
if (this.plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) {
this.plugin.getLogger().info("Processing pre-login (sync phase) for " + uniqueId + " - " + username);
}
event.register(new AsyncConfigurationTask(this.plugin, USER_LOGIN_TASK_TYPE, () -> CompletableFuture.runAsync(() -> {
onPlayerNegotiationAsync(event.getListener().getConnection(), uniqueId, username);
}, this.plugin.getBootstrap().getScheduler().async())));
}
private void onPlayerNegotiationAsync(Connection connection, UUID uniqueId, String username) {
if (this.plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) {
this.plugin.getLogger().info("Processing pre-login (async phase) for " + uniqueId + " - " + username);
}
/* 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(uniqueId, username);
recordConnection(uniqueId);
this.plugin.getEventDispatcher().dispatchPlayerLoginProcess(uniqueId, username, user);
} catch (Exception ex) {
this.plugin.getLogger().severe("Exception occurred whilst loading data for " + uniqueId + " - " + username, ex);
if (this.plugin.getConfiguration().get(ConfigKeys.CANCEL_FAILED_LOGINS)) {
Component component = TranslationManager.render(Message.LOADING_DATABASE_ERROR.build());
connection.send(new ClientboundLoginDisconnectPacket(NeoForgeSenderFactory.toNativeText(component)));
connection.disconnect(NeoForgeSenderFactory.toNativeText(component));
this.plugin.getEventDispatcher().dispatchPlayerLoginProcess(uniqueId, username, null);
}
}
}
@SubscribeEvent(priority = EventPriority.HIGHEST)
public void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) {
ServerPlayer player = (ServerPlayer) event.getEntity();
GameProfile profile = player.getGameProfile();
if (this.plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) {
this.plugin.getLogger().info("Processing post-login for " + profile.getId() + " - " + profile.getName());
}
User user = this.plugin.getUserManager().getIfLoaded(profile.getId());
if (user == null) {
if (!getUniqueConnections().contains(profile.getId())) {
this.plugin.getLogger().warn("User " + profile.getId() + " - " + profile.getName() +
" doesn't have data pre-loaded, they have never been processed during pre-login in this session.");
} else {
this.plugin.getLogger().warn("User " + profile.getId() + " - " + profile.getName() +
" doesn't currently have data pre-loaded, but they have been processed before in this session.");
}
Component component = TranslationManager.render(Message.LOADING_STATE_ERROR.build(), player.getLanguage());
if (this.plugin.getConfiguration().get(ConfigKeys.CANCEL_FAILED_LOGINS)) {
player.connection.disconnect(NeoForgeSenderFactory.toNativeText(component));
return;
} else {
player.sendSystemMessage(NeoForgeSenderFactory.toNativeText(component));
}
}
// initialise capability
UserCapabilityImpl userCapability = UserCapabilityImpl.get(player);
userCapability.initialise(user, player, this.plugin.getContextManager());
this.plugin.getContextManager().signalContextUpdate(player);
}
@SubscribeEvent(priority = EventPriority.LOWEST)
public void onPlayerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) {
ServerPlayer player = (ServerPlayer) event.getEntity();
handleDisconnect(player.getGameProfile().getId());
}
}

View File

@ -0,0 +1,92 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge.listeners;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.context.ParsedCommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.neoforge.LPNeoForgePlugin;
import me.lucko.luckperms.neoforge.util.BrigadierInjector;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.server.players.ServerOpList;
import java.io.IOException;
import java.util.Locale;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.neoforge.event.AddReloadListenerEvent;
import net.neoforged.neoforge.event.CommandEvent;
import net.neoforged.neoforge.event.server.ServerStartedEvent;
public class NeoForgePlatformListener {
private final LPNeoForgePlugin plugin;
public NeoForgePlatformListener(LPNeoForgePlugin plugin) {
this.plugin = plugin;
}
@SubscribeEvent
public void onCommand(CommandEvent event) {
CommandContextBuilder<CommandSourceStack> context = event.getParseResults().getContext();
if (!this.plugin.getConfiguration().get(ConfigKeys.OPS_ENABLED)) {
for (ParsedCommandNode<CommandSourceStack> node : context.getNodes()) {
if (!(node.getNode() instanceof LiteralCommandNode)) {
continue;
}
String name = node.getNode().getName().toLowerCase(Locale.ROOT);
if (name.equals("op") || name.equals("deop")) {
Message.OP_DISABLED.send(this.plugin.getSenderFactory().wrap(context.getSource()));
event.setCanceled(true);
return;
}
}
}
}
@SubscribeEvent
public void onAddReloadListener(AddReloadListenerEvent event) {
Commands commands = event.getServerResources().getCommands();
BrigadierInjector.inject(this.plugin, commands.getDispatcher());
}
@SubscribeEvent
public void onServerStarted(ServerStartedEvent event) {
if (!this.plugin.getConfiguration().get(ConfigKeys.OPS_ENABLED)) {
ServerOpList ops = event.getServer().getPlayerList().getOps();
ops.getEntries().clear();
try {
ops.save();
} catch (IOException ex) {
this.plugin.getLogger().severe("Encountered an error while saving ops", ex);
}
}
}
}

View File

@ -0,0 +1,70 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge.messaging;
import me.lucko.luckperms.common.messaging.InternalMessagingService;
import me.lucko.luckperms.common.messaging.LuckPermsMessagingService;
import me.lucko.luckperms.common.messaging.MessagingFactory;
import me.lucko.luckperms.neoforge.LPNeoForgePlugin;
import net.luckperms.api.messenger.IncomingMessageConsumer;
import net.luckperms.api.messenger.Messenger;
import net.luckperms.api.messenger.MessengerProvider;
import org.checkerframework.checker.nullness.qual.NonNull;
public class NeoForgeMessagingFactory extends MessagingFactory<LPNeoForgePlugin> {
public NeoForgeMessagingFactory(LPNeoForgePlugin plugin) {
super(plugin);
}
@Override
protected InternalMessagingService getServiceFor(String messagingType) {
if (messagingType.equals("pluginmsg") || messagingType.equals("bungee") || messagingType.equals("velocity")) {
try {
return new LuckPermsMessagingService(getPlugin(), new PluginMessageMessengerProvider());
} catch (Exception e) {
getPlugin().getLogger().severe("Exception occurred whilst enabling messaging", e);
}
}
return super.getServiceFor(messagingType);
}
private class PluginMessageMessengerProvider implements MessengerProvider {
@Override
public @NonNull String getName() {
return "PluginMessage";
}
@Override
public @NonNull Messenger obtain(@NonNull IncomingMessageConsumer incomingMessageConsumer) {
PluginMessageMessenger messenger = new PluginMessageMessenger(getPlugin(), incomingMessageConsumer);
messenger.init();
return messenger;
}
}
}

View File

@ -0,0 +1,114 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge.messaging;
import com.google.common.collect.Iterables;
import me.lucko.luckperms.common.messaging.pluginmsg.AbstractPluginMessageMessenger;
import me.lucko.luckperms.common.plugin.scheduler.SchedulerTask;
import me.lucko.luckperms.neoforge.LPNeoForgePlugin;
import net.luckperms.api.messenger.IncomingMessageConsumer;
import net.luckperms.api.messenger.Messenger;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.players.PlayerList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.neoforge.network.PacketDistributor;
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
import net.neoforged.neoforge.network.registration.HandlerThread;
public class PluginMessageMessenger extends AbstractPluginMessageMessenger implements Messenger {
private static final ResourceLocation CHANNEL_ID = ResourceLocation.parse(AbstractPluginMessageMessenger.CHANNEL);
private static final CustomPacketPayload.Type<MessageWrapper> PAYLOAD_TYPE = new CustomPacketPayload.Type<>(CHANNEL_ID);
private final LPNeoForgePlugin plugin;
public PluginMessageMessenger(LPNeoForgePlugin plugin, IncomingMessageConsumer consumer) {
super(consumer);
this.plugin = plugin;
}
@SubscribeEvent
private void register(final RegisterPayloadHandlersEvent event) {
event.registrar("1").executesOn(HandlerThread.NETWORK).commonBidirectional(
PAYLOAD_TYPE,
StreamCodec.of(
(bytebuf, wrapper) -> bytebuf.writeBytes(wrapper.bytes),
buf -> {
byte[] bytes = new byte[buf.readableBytes()];
return new MessageWrapper(bytes);
}
),
(payload, context) -> handleIncomingMessage(payload.bytes())
);
}
public void init() {
this.plugin.getBootstrap().registerListeners(this);
}
@Override
protected void sendOutgoingMessage(byte[] buf) {
AtomicReference<SchedulerTask> taskRef = new AtomicReference<>();
SchedulerTask task = this.plugin.getBootstrap().getScheduler().asyncRepeating(() -> {
ServerPlayer player = this.plugin.getBootstrap().getServer()
.map(MinecraftServer::getPlayerList)
.map(PlayerList::getPlayers)
.map(players -> Iterables.getFirst(players, null))
.orElse(null);
if (player == null) {
return;
}
PacketDistributor.sendToPlayer(player, new MessageWrapper(buf));
SchedulerTask t = taskRef.getAndSet(null);
if (t != null) {
t.cancel();
}
}, 10, TimeUnit.SECONDS);
taskRef.set(task);
}
public static void registerChannel() {
// do nothing - the channels are registered in the static initializer, we just
// need to make sure that is called (which it will be if this method runs)
}
public record MessageWrapper(byte[] bytes) implements CustomPacketPayload {
@Override
public Type<? extends CustomPacketPayload> type() {
return PAYLOAD_TYPE;
}
}
}

View File

@ -0,0 +1,166 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge.service;
import me.lucko.luckperms.common.cacheddata.type.MetaCache;
import me.lucko.luckperms.common.cacheddata.type.PermissionCache;
import me.lucko.luckperms.common.context.ImmutableContextSetImpl;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.verbose.event.CheckOrigin;
import me.lucko.luckperms.neoforge.LPNeoForgeBootstrap;
import me.lucko.luckperms.neoforge.LPNeoForgePlugin;
import me.lucko.luckperms.neoforge.capabilities.UserCapabilityImpl;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.query.QueryMode;
import net.luckperms.api.query.QueryOptions;
import net.luckperms.api.util.Tristate;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import net.neoforged.neoforge.server.permission.handler.IPermissionHandler;
import net.neoforged.neoforge.server.permission.nodes.PermissionDynamicContext;
import net.neoforged.neoforge.server.permission.nodes.PermissionNode;
import net.neoforged.neoforge.server.permission.nodes.PermissionType;
import net.neoforged.neoforge.server.permission.nodes.PermissionTypes;
public class NeoForgePermissionHandler implements IPermissionHandler {
public static final ResourceLocation IDENTIFIER = ResourceLocation.fromNamespaceAndPath(LPNeoForgeBootstrap.ID, "permission_handler");
private final LPNeoForgePlugin plugin;
private final Set<PermissionNode<?>> permissionNodes;
public NeoForgePermissionHandler(LPNeoForgePlugin plugin, Collection<PermissionNode<?>> permissionNodes) {
this.plugin = plugin;
this.permissionNodes = Collections.unmodifiableSet(new HashSet<>(permissionNodes));
for (PermissionNode<?> node : this.permissionNodes) {
this.plugin.getPermissionRegistry().insert(node.getNodeName());
}
}
@Override
public ResourceLocation getIdentifier() {
return IDENTIFIER;
}
@Override
public Set<PermissionNode<?>> getRegisteredNodes() {
return this.permissionNodes;
}
@Override
public <T> T getPermission(ServerPlayer player, PermissionNode<T> node, PermissionDynamicContext<?>... context) {
UserCapabilityImpl capability = UserCapabilityImpl.getNullable(player);
if (capability != null) {
User user = capability.getUser();
QueryOptions queryOptions = capability.getQueryOptionsCache().getQueryOptions();
T value = getPermissionValue(user, queryOptions, node, context);
if (value != null) {
return value;
}
}
return node.getDefaultResolver().resolve(player, player.getUUID(), context);
}
@Override
public <T> T getOfflinePermission(UUID player, PermissionNode<T> node, PermissionDynamicContext<?>... context) {
User user = this.plugin.getUserManager().getIfLoaded(player);
if (user != null) {
QueryOptions queryOptions = user.getQueryOptions();
T value = getPermissionValue(user, queryOptions, node, context);
if (value != null) {
return value;
}
}
return node.getDefaultResolver().resolve(null, player, context);
}
@SuppressWarnings("unchecked")
private static <T> T getPermissionValue(User user, QueryOptions queryOptions, PermissionNode<T> node, PermissionDynamicContext<?>... context) {
queryOptions = appendContextToQueryOptions(queryOptions, context);
String key = node.getNodeName();
PermissionType<T> type = node.getType();
// permission check
if (type == PermissionTypes.BOOLEAN) {
PermissionCache cache = user.getCachedData().getPermissionData(queryOptions);
Tristate value = cache.checkPermission(key, CheckOrigin.PLATFORM_API_HAS_PERMISSION).result();
if (value != Tristate.UNDEFINED) {
return (T) (Boolean) value.asBoolean();
}
}
// meta lookup
if (node.getType() == PermissionTypes.STRING) {
MetaCache cache = user.getCachedData().getMetaData(queryOptions);
String value = cache.getMetaOrChatMetaValue(node.getNodeName(), CheckOrigin.PLATFORM_API);
if (value != null) {
return (T) value;
}
}
// meta lookup (integer)
if (node.getType() == PermissionTypes.INTEGER) {
MetaCache cache = user.getCachedData().getMetaData(queryOptions);
String value = cache.getMetaOrChatMetaValue(node.getNodeName(), CheckOrigin.PLATFORM_API);
if (value != null) {
try {
return (T) Integer.valueOf(Integer.parseInt(value));
} catch (IllegalArgumentException e) {
// ignore
}
}
}
return null;
}
private static QueryOptions appendContextToQueryOptions(QueryOptions queryOptions, PermissionDynamicContext<?>... context) {
if (context.length == 0 || queryOptions.mode() != QueryMode.CONTEXTUAL) {
return queryOptions;
}
ImmutableContextSet.Builder contextBuilder = new ImmutableContextSetImpl.BuilderImpl()
.addAll(queryOptions.context());
for (PermissionDynamicContext<?> dynamicContext : context) {
contextBuilder.add(dynamicContext.getDynamic().name(), dynamicContext.getSerializedValue());
}
return queryOptions.toBuilder().context(contextBuilder.build()).build();
}
}

View File

@ -0,0 +1,64 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge.service;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.neoforge.LPNeoForgePlugin;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.neoforge.common.ModConfigSpec;
import net.neoforged.neoforge.common.NeoForgeConfig;
import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent;
import net.neoforged.neoforge.server.permission.handler.DefaultPermissionHandler;
import net.neoforged.neoforge.server.permission.nodes.PermissionNode;
import net.neoforged.neoforge.server.permission.nodes.PermissionTypes;
public class NeoForgePermissionHandlerListener {
private final LPNeoForgePlugin plugin;
public NeoForgePermissionHandlerListener(LPNeoForgePlugin plugin) {
this.plugin = plugin;
}
@SubscribeEvent
public void onPermissionGatherHandler(PermissionGatherEvent.Handler event) {
// Override the default permission handler with LuckPerms
ModConfigSpec.ConfigValue<String> permissionHandler = NeoForgeConfig.SERVER.permissionHandler;
if (permissionHandler.get().equals(DefaultPermissionHandler.IDENTIFIER.toString())) {
permissionHandler.set(NeoForgePermissionHandler.IDENTIFIER.toString());
}
event.addPermissionHandler(NeoForgePermissionHandler.IDENTIFIER, permissions -> new NeoForgePermissionHandler(this.plugin, permissions));
}
@SubscribeEvent
public void onPermissionGatherNodes(PermissionGatherEvent.Nodes event) {
// register luckperms nodes
for (CommandPermission permission : CommandPermission.values()) {
event.addNodes(new PermissionNode<>("luckperms", permission.getNode(), PermissionTypes.BOOLEAN, (player, uuid, context) -> false));
}
}
}

View File

@ -0,0 +1,61 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge.util;
import me.lucko.luckperms.neoforge.LPNeoForgePlugin;
import net.minecraft.network.protocol.Packet;
import net.minecraft.server.network.ConfigurationTask;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class AsyncConfigurationTask implements ConfigurationTask {
private final LPNeoForgePlugin plugin;
private final Type type;
private final Supplier<CompletableFuture<?>> task;
public AsyncConfigurationTask(LPNeoForgePlugin plugin, Type type, Supplier<CompletableFuture<?>> task) {
this.plugin = plugin;
this.type = type;
this.task = task;
}
@Override
public void start(Consumer<Packet<?>> send) {
CompletableFuture<?> future = this.task.get();
future.whenCompleteAsync((o, e) -> {
if (e != null) {
this.plugin.getLogger().warn("Configuration task threw an exception", e);
}
}).join();
}
@Override
public Type type() {
return this.type;
}
}

View File

@ -0,0 +1,197 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge.util;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import me.lucko.luckperms.common.graph.Graph;
import me.lucko.luckperms.common.graph.TraversalAlgorithm;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.neoforge.LPNeoForgePlugin;
import me.lucko.luckperms.neoforge.capabilities.UserCapability;
import me.lucko.luckperms.neoforge.capabilities.UserCapabilityImpl;
import net.luckperms.api.util.Tristate;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.server.level.ServerPlayer;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Locale;
import java.util.function.Predicate;
/**
* Utility for injecting permission requirements into a Brigadier command tree.
*/
public final class BrigadierInjector {
private BrigadierInjector() {}
private static final Field REQUIREMENT_FIELD;
static {
Field requirementField;
try {
requirementField = CommandNode.class.getDeclaredField("requirement");
requirementField.setAccessible(true);
} catch (NoSuchFieldException e) {
throw new ExceptionInInitializerError(e);
}
REQUIREMENT_FIELD = requirementField;
}
/**
* Inject permission requirements into the commands in the given dispatcher.
*
* @param plugin the plugin
* @param dispatcher the command dispatcher
*/
public static void inject(LPNeoForgePlugin plugin, CommandDispatcher<CommandSourceStack> dispatcher) {
Iterable<CommandNodeWithParent> tree = CommandNodeGraph.INSTANCE.traverse(
TraversalAlgorithm.DEPTH_FIRST_PRE_ORDER,
new CommandNodeWithParent(null, dispatcher.getRoot())
);
for (CommandNodeWithParent node : tree) {
Predicate<CommandSourceStack> requirement = node.node.getRequirement();
// already injected - skip
if (requirement instanceof InjectedPermissionRequirement) {
continue;
}
String permission = buildPermissionNode(node);
if (permission == null) {
continue;
}
plugin.getPermissionRegistry().insert(permission);
InjectedPermissionRequirement newRequirement = new InjectedPermissionRequirement(plugin, permission, requirement);
try {
REQUIREMENT_FIELD.set(node.node, newRequirement);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
private static String buildPermissionNode(CommandNodeWithParent node) {
StringBuilder builder = new StringBuilder();
while (node != null) {
if (node.node instanceof LiteralCommandNode) {
if (builder.length() != 0) {
builder.insert(0, '.');
}
String name = node.node.getName().toLowerCase(Locale.ROOT);
builder.insert(0, name);
}
node = node.parent;
}
if (builder.length() == 0) {
return null;
}
builder.insert(0, "command.");
return builder.toString();
}
/**
* Injected {@link CommandNode#getRequirement() requirement} that checks for a permission, before
* delegating to the existing requirement.
*/
private static final class InjectedPermissionRequirement implements Predicate<CommandSourceStack> {
private final LPNeoForgePlugin plugin;
private final String permission;
private final Predicate<CommandSourceStack> delegate;
private InjectedPermissionRequirement(LPNeoForgePlugin plugin, String permission, Predicate<CommandSourceStack> delegate) {
this.plugin = plugin;
this.permission = permission;
this.delegate = delegate;
}
@Override
public boolean test(CommandSourceStack source) {
if (source.getEntity() instanceof ServerPlayer) {
ServerPlayer player = (ServerPlayer) source.getEntity();
Tristate state = Tristate.UNDEFINED;
// If player is still connecting and has not been added to world then check LP user directly
if (!player.isAddedToLevel()) {
User user = this.plugin.getUserManager().getIfLoaded(player.getUUID());
if (user == null) {
// Should never happen but just in case...
return false;
}
state = user.getCachedData().getPermissionData().checkPermission(permission);
} else {
UserCapability user = UserCapabilityImpl.get(player);
state = user.checkPermission(this.permission);
}
if (state != Tristate.UNDEFINED) {
return state.asBoolean() && this.delegate.test(source.withPermission(4));
}
}
return this.delegate.test(source);
}
}
/**
* A {@link Graph} to represent the brigadier command node tree.
*/
private enum CommandNodeGraph implements Graph<CommandNodeWithParent> {
INSTANCE;
@Override
public Iterable<? extends CommandNodeWithParent> successors(CommandNodeWithParent ctx) {
CommandNode<CommandSourceStack> node = ctx.node;
Collection<CommandNodeWithParent> successors = new ArrayList<>();
for (CommandNode<CommandSourceStack> child : node.getChildren()) {
successors.add(new CommandNodeWithParent(ctx, child));
}
return successors;
}
}
private static final class CommandNodeWithParent {
private final CommandNodeWithParent parent;
private final CommandNode<CommandSourceStack> node;
private CommandNodeWithParent(CommandNodeWithParent parent, CommandNode<CommandSourceStack> node) {
this.parent = parent;
this.node = node;
}
}
}

View File

@ -0,0 +1,241 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.neoforge.util;
import me.lucko.luckperms.common.loader.JarInJarClassLoader;
import net.neoforged.bus.api.Event;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.ModLoadingContext;
import net.neoforged.fml.event.IModBusEvent;
import java.lang.invoke.CallSite;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import net.neoforged.neoforge.common.NeoForge;
/**
* A utility for registering Forge listeners for methods in a jar-in-jar.
*
* <p>This differs from {@link IEventBus#register} as reflection is used for invoking the registered listeners
* instead of ASM, which is incompatible with {@link JarInJarClassLoader}</p>
*/
public class NeoForgeEventBusFacade {
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
private final List<ListenerRegistration> listeners = new ArrayList<>();
/**
* Register listeners for all methods annotated with {@link SubscribeEvent} on the target object.
*/
public void register(Object target) {
for (Method method : target.getClass().getMethods()) {
// Ignore static methods, Support for these could be added, but they are not used in LuckPerms
if (Modifier.isStatic(method.getModifiers())) {
continue;
}
// Methods require a SubscribeEvent annotation in order to be registered
SubscribeEvent subscribeEvent = method.getAnnotation(SubscribeEvent.class);
if (subscribeEvent == null) {
continue;
}
EventType type = determineListenerType(method);
Consumer<?> invoker = createInvokerFunction(method, target, type);
// Determine the 'IEventBus' that this eventType should be registered to.
IEventBus eventBus;
if (IModBusEvent.class.isAssignableFrom(type.eventType)) {
eventBus = ModLoadingContext.get().getActiveContainer().getEventBus();
} else {
eventBus = NeoForge.EVENT_BUS;
}
addListener(eventBus, subscribeEvent, type.eventType, invoker);
this.listeners.add(new ListenerRegistration(invoker, eventBus, target));
}
}
/**
* Unregister previously registered listeners on the target object.
*
* @param target the target listener
*/
public void unregister(Object target) {
this.listeners.removeIf(listener -> {
if (listener.target == target) {
listener.close();
return true;
} else {
return false;
}
});
}
/**
* Unregister all listeners created through this interface.
*/
public void unregisterAll() {
for (ListenerRegistration listener : this.listeners) {
listener.close();
}
this.listeners.clear();
}
/**
* A listener registration.
*/
private static final class ListenerRegistration implements AutoCloseable {
/** The lambda invoker function */
private final Consumer<?> invoker;
/** The event bus that the invoker was registered to */
private final IEventBus eventBus;
/** The target listener class */
private final Object target;
private ListenerRegistration(Consumer<?> invoker, IEventBus eventBus, Object target) {
this.invoker = invoker;
this.eventBus = eventBus;
this.target = target;
}
@Override
public void close() {
this.eventBus.unregister(this.invoker);
}
}
private static Consumer<?> createInvokerFunction(Method method, Object target, EventType type) {
// Use the 'LambdaMetafactory' to generate a consumer which can be passed directly to an 'IEventBus'
// when registering a listener, this reduces the overhead involved when reflectively invoking methods.
try {
MethodHandle methodHandle = LOOKUP.unreflect(method);
CallSite callSite = LambdaMetafactory.metafactory(
LOOKUP,
"accept",
MethodType.methodType(Consumer.class, target.getClass()),
MethodType.methodType(void.class, Object.class),
methodHandle,
MethodType.methodType(void.class, type.eventType)
);
return (Consumer<?>) callSite.getTarget().bindTo(target).invokeExact();
} catch (Throwable t) {
throw new RuntimeException("Error whilst registering " + method, t);
}
}
public static EventType determineListenerType(Method method) {
// Get the parameter types, this includes generic information which is required for GenericEvent
Type[] parameterTypes = method.getGenericParameterTypes();
if (parameterTypes.length != 1) {
throw new IllegalArgumentException(""
+ "Method " + method + " has @SubscribeEvent annotation. "
+ "It has " + parameterTypes.length + " arguments, "
+ "but event handler methods require a single argument only."
);
}
Type parameterType = parameterTypes[0];
Class<?> eventType;
Class<?> genericType;
if (parameterType instanceof Class) { // Non-generic event
eventType = (Class<?>) parameterType;
genericType = null;
} else if (parameterType instanceof ParameterizedType) { // Generic event
ParameterizedType parameterizedType = (ParameterizedType) parameterType;
// Get the event class
Type rawType = parameterizedType.getRawType();
if (rawType instanceof Class) {
eventType = (Class<?>) rawType;
} else {
throw new UnsupportedOperationException("Raw Type " + rawType.getClass() + " is not supported");
}
// Find the type of 'T' in 'GenericEvent<T>'
Type[] typeArguments = parameterizedType.getActualTypeArguments();
if (typeArguments.length != 1) {
throw new IllegalArgumentException(""
+ "Method " + method + " has @SubscribeEvent annotation. "
+ "It has a " + eventType + " argument, "
+ "but generic events require a single type argument only."
);
}
// Get the generic class
Type typeArgument = typeArguments[0];
if (typeArgument instanceof Class<?>) {
genericType = (Class<?>) typeArgument;
} else {
throw new UnsupportedOperationException("Type Argument " + typeArgument.getClass() + " is not supported");
}
} else {
throw new UnsupportedOperationException("Parameter Type " + parameterType.getClass() + " is not supported");
}
// Ensure 'eventType' is a subclass of event
if (!Event.class.isAssignableFrom(eventType)) {
throw new IllegalArgumentException(""
+ "Method " + method + " has @SubscribeEvent annotation, "
+ "but takes an argument that is not an Event subtype: " + eventType
);
}
return new EventType(eventType, genericType);
}
private static final class EventType {
private final Class<?> eventType;
private final Class<?> genericType;
private EventType(Class<?> eventType, Class<?> genericType) {
this.eventType = eventType;
this.genericType = genericType;
}
}
/**
* Handles casting generics for {@link IEventBus#addListener}.
*/
@SuppressWarnings("unchecked")
private static <T extends Event> void addListener(IEventBus eventBus, SubscribeEvent annotation, Class<?> eventType, Consumer<?> consumer) {
eventBus.addListener(annotation.priority(), annotation.receiveCanceled(), (Class<T>) eventType, (Consumer<T>) consumer);
}
}

View File

@ -0,0 +1,641 @@
####################################################################################################
# +----------------------------------------------------------------------------------------------+ #
# | __ __ ___ __ __ | #
# | | | | / ` |__/ |__) |__ |__) |\/| /__` | #
# | |___ \__/ \__, | \ | |___ | \ | | .__/ | #
# | | #
# | https://luckperms.net | #
# | | #
# | WIKI: https://luckperms.net/wiki | #
# | DISCORD: https://discord.gg/luckperms | #
# | BUG REPORTS: https://github.com/LuckPerms/LuckPerms/issues | #
# | | #
# | Each option in this file is documented and explained here: | #
# | ==> https://luckperms.net/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. | #
# +----------------------------------------------------------------------------------------------+ #
####################################################################################################
# +----------------------------------------------------------------------------------------------+ #
# | | #
# | ESSENTIAL SETTINGS | #
# | | #
# | Important settings that control how LuckPerms functions. | #
# | | #
# +----------------------------------------------------------------------------------------------+ #
# The name of the server, used for server specific permissions.
#
# - When set to "global" this setting is effectively ignored.
# - In all other cases, the value here is added to all players in a "server" context.
# - See: https://luckperms.net/wiki/Context
server = "global"
# If the servers own UUID cache/lookup facility should be used when there is no record for a player
# already in LuckPerms.
#
# - When this is set to 'false', commands using a player's username will not work unless the player
# has joined since LuckPerms was first installed.
# - To get around this, you can use a player's uuid directly in the command, or enable this option.
# - When this is set to 'true', the server facility is used. This may use a number of methods,
# including checking the servers local cache, or making a request to the Mojang API.
use-server-uuid-cache = false
# +----------------------------------------------------------------------------------------------+ #
# | | #
# | STORAGE SETTINGS | #
# | | #
# | Controls which storage method LuckPerms will use to store data. | #
# | | #
# +----------------------------------------------------------------------------------------------+ #
# How the plugin should store data
#
# - The various options are explained in more detail on the wiki:
# https://luckperms.net/wiki/Storage-types
#
# - Possible options:
#
# | Remote databases - require connection information to be configured below
# |=> MySQL
# |=> MariaDB (preferred over MySQL)
# |=> PostgreSQL
# |=> MongoDB
#
# | Flatfile/local database - don't require any extra configuration
# |=> H2 (preferred over SQLite)
# |=> SQLite
#
# | Readable & editable text files - don't require any extra configuration
# |=> YAML (.yml files)
# |=> JSON (.json files)
# |=> HOCON (.conf files)
# |=> TOML (.toml files)
# |
# | By default, user, group and track data is separated into different files. Data can be combined
# | and all stored in the same file by switching to a combined storage variant.
# | Just add '-combined' to the end of the storage-method, e.g. 'yaml-combined'
#
# - A H2 database is the default option.
# - If you want to edit data manually in "traditional" storage files, we suggest using YAML.
storage-method = "h2"
# The following block defines the settings for remote database storage methods.
#
# - You don't need to touch any of the settings here if you're using a local storage method!
# - The connection detail options are shared between all remote storage types.
data {
# Define the address and port for the database.
# - The standard DB engine port is used by default
# (MySQL = 3306, PostgreSQL = 5432, MongoDB = 27017)
# - Specify as "host:port" if differs
address = "localhost"
# The name of the database to store LuckPerms data in.
# - This must be created already. Don't worry about this setting if you're using MongoDB.
database = "minecraft"
# Credentials for the database.
username = "root"
password = ""
# These settings apply to the MySQL connection pool.
# - The default values will be suitable for the majority of users.
# - Do not change these settings unless you know what you're doing!
pool-settings {
# Sets the maximum size of the MySQL connection pool.
# - Basically this value will determine the maximum number of actual
# connections to the database backend.
# - More information about determining the size of connection pools can be found here:
# https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing
maximum-pool-size = 10
# Sets the minimum number of idle connections that the pool will try to maintain.
# - For maximum performance and responsiveness to spike demands, it is recommended to not set
# this value and instead allow the pool to act as a fixed size connection pool.
# (set this value to the same as 'maximum-pool-size')
minimum-idle = 10
# This setting controls the maximum lifetime of a connection in the pool in milliseconds.
# - The value should be at least 30 seconds less than any database or infrastructure imposed
# connection time limit.
maximum-lifetime = 1800000 # 30 minutes
# This setting controls how frequently the pool will 'ping' a connection in order to prevent it
# from being timed out by the database or network infrastructure, measured in milliseconds.
# - The value should be less than maximum-lifetime and greater than 30000 (30 seconds).
# - Setting the value to zero will disable the keepalive functionality.
keepalive-time = 0
# This setting controls the maximum number of milliseconds that the plugin will wait for a
# connection from the pool, before timing out.
connection-timeout = 5000 # 5 seconds
# This setting allows you to define extra properties for connections.
#
# By default, the following options are set to enable utf8 encoding. (you may need to remove
# these if you are using PostgreSQL)
# useUnicode = true
# characterEncoding = "utf8"
#
# You can also use this section to disable SSL connections, by uncommenting the 'useSSL' and
# 'verifyServerCertificate' options below.
properties {
useUnicode = true
characterEncoding = "utf8"
#useSSL: false
#verifyServerCertificate: false
}
}
# The prefix for all LuckPerms SQL tables.
#
# - This only applies for remote SQL storage types (MySQL, MariaDB, etc).
# - Change this if you want to use different tables for different servers.
table-prefix = "luckperms_"
# The prefix to use for all LuckPerms MongoDB collections.
#
# - This only applies for the MongoDB storage type.
# - Change this if you want to use different collections for different servers. The default is no
# prefix.
mongodb-collection-prefix = ""
# The connection string URI to use to connect to the MongoDB instance.
#
# - When configured, this setting will override anything defined in the address, database,
# username or password fields above.
# - If you have a connection string that starts with 'mongodb://' or 'mongodb+srv://', enter it
# below.
# - For more information, please see https://docs.mongodb.com/manual/reference/connection-string/
mongodb-connection-uri = ""
}
# Define settings for a "split" storage setup.
#
# - This allows you to define a storage method for each type of data.
# - The connection options above still have to be correct for each type here.
split-storage {
# Don't touch this if you don't want to use split storage!
enabled = false
methods {
# These options don't need to be modified if split storage isn't enabled.
user = "h2"
group = "h2"
track = "h2"
uuid = "h2"
log = "h2"
}
}
# +----------------------------------------------------------------------------------------------+ #
# | | #
# | UPDATE PROPAGATION & MESSAGING SERVICE | #
# | | #
# | Controls the ways in which LuckPerms will sync data & notify other servers of changes. | #
# | These options are documented on greater detail on the wiki under "Instant Updates". | #
# | | #
# +----------------------------------------------------------------------------------------------+ #
# This option controls how frequently LuckPerms will perform a sync task.
#
# - A sync task will refresh all data from the storage, and ensure that the most up-to-date data is
# being used by the plugin.
# - This is disabled by default, as most users will not need it. However, if you're using a remote
# storage type without a messaging service setup, you may wish to set this to something like 3.
# - Set to -1 to disable the task completely.
sync-minutes = -1
# If the file watcher should be enabled.
#
# - When using a file-based storage type, LuckPerms can monitor the data files for changes, and
# automatically update when changes are detected.
# - If you don't want this feature to be active, set this option to false.
watch-files = true
# Define which messaging service should be used by the plugin.
#
# - If enabled and configured, LuckPerms will use the messaging service to inform other connected
# servers of changes.
# - Use the command "/lp networksync" to manually push changes.
# - Data is NOT stored using this service. It is only used as a messaging platform.
#
# - If you decide to enable this feature, you should set "sync-minutes" to -1, as there is no need
# for LuckPerms to poll the database for changes.
#
# - Possible options:
# => sql Uses the SQL database to form a queue system for communication. Will only work when
# 'storage-method' is set to MySQL or MariaDB. This is chosen by default if the
# option is set to 'auto' and SQL storage is in use. Set to 'notsql' to disable this.
# => pluginmsg Uses the plugin messaging channels to communicate with the proxy.
# LuckPerms must be installed on your proxy & all connected servers backend servers.
# Won't work if you have more than one proxy.
# => redis Uses Redis pub-sub to push changes. Your server connection info must be configured
# below.
# => rabbitmq Uses RabbitMQ pub-sub to push changes. Your server connection info must be
# configured below.
# => custom Uses a messaging service provided using the LuckPerms API.
# => auto Attempts to automatically setup a messaging service using redis or sql.
messaging-service = "auto"
# If LuckPerms should automatically push updates after a change has been made with a command.
auto-push-updates = true
# If LuckPerms should push logging entries to connected servers via the messaging service.
push-log-entries = true
# If LuckPerms should broadcast received logging entries to players on this platform.
#
# - If you have LuckPerms installed on your backend servers as well as a BungeeCord proxy, you
# should set this option to false on either your backends or your proxies, to avoid players being
# messaged twice about log entries.
broadcast-received-log-entries = true
# Settings for Redis.
# Port 6379 is used by default; set address to "host:port" if differs
# Multiple Redis nodes can be specified in the same format as a string list under the name "addresses".
redis {
enabled = false
address = "localhost"
username = ""
password = ""
}
# Settings for RabbitMQ.
# Port 5672 is used by default; set address to "host:port" if differs
rabbitmq {
enabled = false
address = "localhost"
vhost = "/"
username = "guest"
password = "guest"
}
# +----------------------------------------------------------------------------------------------+ #
# | | #
# | CUSTOMIZATION SETTINGS | #
# | | #
# | Settings that allow admins to customize the way LuckPerms operates. | #
# | | #
# +----------------------------------------------------------------------------------------------+ #
# Controls how temporary permissions/parents/meta should be accumulated.
#
# - The default behaviour is "deny".
# - This behaviour can also be specified when the command is executed. See the command usage
# documentation for more info.
#
# - Possible options:
# => accumulate durations will be added to the existing expiry time
# => replace durations will be replaced if the new duration is later than the current
# expiration
# => deny the command will just fail if you try to add another node with the same expiry
temporary-add-behaviour = "deny"
# Controls how LuckPerms will determine a users "primary" group.
#
# - The meaning and influence of "primary groups" are explained in detail on the wiki.
# - The preferred approach is to let LuckPerms automatically determine a users primary group
# based on the relative weight of their parent groups.
#
# - Possible 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 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 nature of the checks are documented on the wiki under "Argument based command permissions".
# - Argument based permissions are *not* static, unlike the 'base' permissions, and will depend upon
# the arguments given within the command.
argument-based-command-permissions = false
# If the plugin should check whether senders are a member of a given group before they're able to
# edit the groups data or add/remove other users to/from it.
# Note: these limitations do not apply to the web editor!
require-sender-group-membership-to-modify = false
# If the plugin should send log notifications to users whenever permissions are modified.
#
# - Notifications are only sent to those with the appropriate permission to receive them
# - They can also be temporarily enabled/disabled on a per-user basis using
# '/lp log notify <on|off>'
log-notify = true
# Defines a list of log entries which should not be sent as notifications to users.
#
# - Each entry in the list is a RegEx expression which is matched against the log entry description.
log-notify-filtered-descriptions = [
# "parent add example"
]
# If LuckPerms should automatically install translation bundles and periodically update them.
auto-install-translations = true
# Defines the options for prefix and suffix stacking.
#
# - The feature allows you to display multiple prefixes or suffixes alongside a players username in
# chat.
# - It is explained and documented in more detail on the wiki under "Prefix & Suffix Stacking".
#
# - The options are divided into separate sections for prefixes and suffixes.
# - The 'duplicates' setting refers to how duplicate elements are handled. Can be 'retain-all',
# 'first-only' or 'last-only'.
# - The value of 'start-spacer' is included at the start of the resultant prefix/suffix.
# - The value of 'end-spacer' is included at the end of the resultant prefix/suffix.
# - The value of 'middle-spacer' is included between each element in the resultant prefix/suffix.
#
# - Possible format options:
# => highest Selects the value with the highest weight, from all values
# held by or inherited by the player.
#
# => lowest Same as above, except takes the one with the lowest weight.
#
# => highest_own Selects the value with the highest weight, but will not
# accept any inherited values.
#
# => lowest_own Same as above, except takes the value with the lowest weight.
#
# => highest_inherited Selects the value with the highest weight, but will only
# accept inherited values.
#
# => lowest_inherited Same as above, except takes the value with the lowest weight.
#
# => highest_on_track_<track> Selects the value with the highest weight, but only if the
# value was inherited from a group on the given track.
#
# => lowest_on_track_<track> Same as above, except takes the value with the lowest weight.
#
# => highest_not_on_track_<track> Selects the value with the highest weight, but only if the
# value was inherited from a group not on the given track.
#
# => lowest_not_on_track_<track> Same as above, except takes the value with the lowest weight.
#
# => highest_from_group_<group> Selects the value with the highest weight, but only if the
# value was inherited from the given group.
#
# => lowest_from_group_<group> Same as above, except takes the value with the lowest weight.
#
# => highest_not_from_group_<group> Selects the value with the highest weight, but only if the
# value was not inherited from the given group.
#
# => lowest_not_from_group_<group> Same as above, except takes the value with the lowest weight.
meta-formatting {
prefix {
format = [
"highest"
]
duplicates = "first-only"
start-spacer = ""
middle-spacer = " "
end-spacer = ""
}
suffix {
format = [
"highest"
]
duplicates = "first-only"
start-spacer = ""
middle-spacer = " "
end-spacer = ""
}
}
# +----------------------------------------------------------------------------------------------+ #
# | | #
# | PERMISSION CALCULATION AND INHERITANCE | #
# | | #
# | Modify the way permission checks, meta lookups and inheritance resolutions are handled. | #
# | | #
# +----------------------------------------------------------------------------------------------+ #
# The algorithm LuckPerms should use when traversing the "inheritance tree".
#
# - Possible options:
# => breadth-first See: https://en.wikipedia.org/wiki/Breadth-first_search
# => depth-first-pre-order See: https://en.wikipedia.org/wiki/Depth-first_search
# => depth-first-post-order See: https://en.wikipedia.org/wiki/Depth-first_search
inheritance-traversal-algorithm = "depth-first-pre-order"
# If a final sort according to "inheritance rules" should be performed after the traversal algorithm
# has resolved the inheritance tree.
#
# "Inheritance rules" refers to things such as group weightings, primary group status, and the
# natural contextual ordering of the group nodes.
#
# Setting this to 'true' will allow for the inheritance rules to take priority over the structure of
# the inheritance tree.
#
# Effectively when this setting is 'true': the tree is flattened, and rules applied afterwards,
# and when this setting is 'false':, the rules are just applied during each step of the traversal.
post-traversal-inheritance-sort = false
# Defines the mode used to determine whether a set of contexts are satisfied.
#
# - Possible options:
# => at-least-one-value-per-key Set A will be satisfied by another set B, if at least one of the
# key-value entries per key in A are also in B.
# => all-values-per-key Set A will be satisfied by another set B, if all key-value
# entries in A are also in B.
context-satisfy-mode = "at-least-one-value-per-key"
# LuckPerms has a number of built-in contexts. These can be disabled by adding the context key to
# the list below.
disabled-contexts = [
# "world"
]
# +----------------------------------------------------------------------------------------------+ #
# | Permission resolution settings | #
# +----------------------------------------------------------------------------------------------+ #
# If users on this server should have their global permissions applied.
# When 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.
# When 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
# +----------------------------------------------------------------------------------------------+ #
# | Meta lookup settings | #
# +----------------------------------------------------------------------------------------------+ #
# Defines how meta values should be selected.
#
# - Possible options:
# => inheritance Selects the meta value that was inherited first
# => highest-number Selects the highest numerical meta value
# => lowest-number Selects the lowest numerical meta value
meta-value-selection-default = "inheritance"
# Defines how meta values should be selected per key.
meta-value-selection {
#max-homes = "highest-number"
}
# +----------------------------------------------------------------------------------------------+ #
# | Inheritance settings | #
# +----------------------------------------------------------------------------------------------+ #
# 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 LuckPerms should resolve and apply permissions according to the Sponge style implicit wildcard
# inheritance system.
#
# - That being: If a user has been granted "example", then the player should have also be
# automatically granted "example.function", "example.another", "example.deeper.nesting",
# and so on.
apply-sponge-implicit-wildcards = false
# 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 owner of an integrated server should bypass permission checks.
#
# - This setting only applies when LuckPerms is active on a single-player world.
# - The owner of an integrated server is the player whose client instance is running the server.
integrated-server-owner-bypasses-checks = true
# +----------------------------------------------------------------------------------------------+ #
# | Extra settings | #
# +----------------------------------------------------------------------------------------------+ #
# A list of context calculators which will be skipped when calculating contexts.
#
# - You can disable context calculators by either:
# => specifying the Java class name used by the calculator (e.g. com.example.ExampleCalculator)
# => specifying a sub-section of the Java package used by the calculator (e.g. com.example)
disabled-context-calculators = []
# Allows you to set "aliases" for the worlds sent forward for context calculation.
#
# - These aliases are provided in addition to the real world name. Applied recursively.
# - Remove the comment characters for the default aliases to apply.
world-rewrite {
#world_nether = "world"
#world_the_end = "world"
}
# Define special group weights for this server.
#
# - Group weights can also be applied directly to group data, using the setweight command.
# - This section allows weights to be set on a per-server basis.
group-weight {
#admin = 10
}
# +----------------------------------------------------------------------------------------------+ #
# | | #
# | FINE TUNING OPTIONS | #
# | | #
# | A number of more niche settings for tweaking and changing behaviour. The section also | #
# | contains toggles for some more specialised features. It is only necessary to make changes to | #
# | these options if you want to fine-tune LuckPerms behaviour. | #
# | | #
# +----------------------------------------------------------------------------------------------+ #
# +----------------------------------------------------------------------------------------------+ #
# | Server Operator (OP) settings | #
# +----------------------------------------------------------------------------------------------+ #
# Controls whether server operators should exist at all.
#
# - When set to 'false', all players will be de-opped, and the /op and /deop commands will be
# disabled. Note that vanilla features like the spawn-protection require an operator on the
# server to work.
enable-ops = true
# Enables or disables a special permission based system in LuckPerms for controlling OP status.
#
# - If set to true, any user with the permission "luckperms.autoop" will automatically be granted
# server operator status. This permission can be inherited, or set on specific servers/worlds,
# temporarily, etc.
# - Additionally, setting this to true will force the "enable-ops" option above to false. All users
# will be de-opped unless they have the permission node, and the op/deop commands will be
# disabled.
# - It is recommended that you use this option instead of assigning a single '*' permission.
# - However, on Forge this setting can be used as a "pseudo" root wildcard, as many mods support
# the operator system over permissions.
auto-op = false
# +----------------------------------------------------------------------------------------------+ #
# | Miscellaneous (and rarely used) settings | #
# +----------------------------------------------------------------------------------------------+ #
# 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 LuckPerms should 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 not require users to confirm bulkupdate operations.
#
# - When set to true, operations will be executed immediately.
# - This is not recommended, as bulkupdate has the potential to irreversibly delete large amounts of
# data, and is not designed to be executed automatically.
# - If automation is needed, users should prefer using the LuckPerms API.
skip-bulkupdate-confirmation = false
# If LuckPerms should prevent bulkupdate operations.
#
# - When set to true, bulkupdate operations (the /lp bulkupdate command) will not work.
# - When set to false, bulkupdate operations will be allowed via the console.
disable-bulkupdate = false
# If LuckPerms should allow a users primary group to be removed with the 'parent remove' command.
#
# - When this happens, the plugin will set their primary group back to default.
prevent-primary-group-removal = false
# If LuckPerms should update the list of commands sent to the client when permissions are changed.
update-client-command-list = true
# If LuckPerms should attempt to resolve Vanilla command target selectors for LP commands.
# See here for more info: https://minecraft.wiki/w/Target_selectors
resolve-command-selectors = false

View File

@ -1,5 +1,6 @@
pluginManagement {
repositories {
gradlePluginPortal()
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
@ -8,7 +9,6 @@ pluginManagement {
name = 'Forge'
url = 'https://maven.minecraftforge.net/'
}
gradlePluginPortal()
}
}
@ -28,6 +28,9 @@ include (
'bungee',
'bungee:loader',
'fabric',
'neoforge',
'neoforge:loader',
'neoforge:neoforge-api',
'forge',
'forge:loader',
'forge:forge-api',