LuckPerms for Forge (#3262)

This commit is contained in:
Alex Thomson 2022-05-21 07:09:04 +12:00 committed by GitHub
parent 65a4c67745
commit 6421525f3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 4016 additions and 74 deletions

View File

@ -23,4 +23,4 @@ The project is split up into a few separate modules.
* **API** - The public, semantically versioned API used by other plugins wishing to integrate with and retrieve data from LuckPerms. This module (for the most part) does not contain any implementation itself, and is provided by the plugin.
* **Common** - The common module contains most of the code which implements the respective LuckPerms plugins. This abstract module reduces duplicated code throughout the project.
* **Bukkit, BungeeCord, Sponge, Nukkit, Velocity & Fabric** - Each use the common module to implement plugins on the respective server platforms.
* **Bukkit, BungeeCord, Fabric, Forge, Nukkit, Sponge & Velocity** - Each use the common module to implement plugins on the respective server platforms.

View File

@ -47,7 +47,7 @@ The project is split up into a few separate modules.
* **API** - The public, semantically versioned API used by other plugins wishing to integrate with and retrieve data from LuckPerms. This module (for the most part) does not contain any implementation itself, and is provided by the plugin.
* **Common** - The common module contains most of the code which implements the respective LuckPerms plugins. This abstract module reduces duplicated code throughout the project.
* **Bukkit, BungeeCord, Sponge, Fabric, Nukkit & Velocity** - Each use the common module to implement plugins on the respective server platforms.
* **Bukkit, BungeeCord, Fabric, Forge, Nukkit, Sponge & Velocity** - Each use the common module to implement plugins on the respective server platforms.
## License
LuckPerms is licensed under the permissive MIT license. Please see [`LICENSE.txt`](https://github.com/lucko/LuckPerms/blob/master/LICENSE.txt) for more info.

View File

@ -75,7 +75,8 @@ public interface Platform {
SPONGE("Sponge"),
NUKKIT("Nukkit"),
VELOCITY("Velocity"),
FABRIC("Fabric");
FABRIC("Fabric"),
FORGE("Forge");
private final String friendlyName;

View File

@ -54,5 +54,6 @@ subprojects {
mavenCentral()
maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
maven { url 'https://repo.lucko.me/' }
maven { url 'https://libraries.minecraft.net/' }
}
}

View File

@ -4,7 +4,6 @@ plugins {
repositories {
maven { url 'https://papermc.io/repo/repository/maven-public/' }
maven { url 'https://libraries.minecraft.net/' }
}
dependencies {

View File

@ -17,6 +17,7 @@ dependencies {
compileOnly project(':common:loader-utils')
compileOnly 'com.mojang:brigadier:1.0.18'
compileOnly 'org.slf4j:slf4j-api:1.7.30'
compileOnly 'org.apache.logging.log4j:log4j-api:2.14.0'

View File

@ -32,8 +32,8 @@ public interface LoaderBootstrap {
void onLoad();
void onEnable();
default void onEnable() {}
void onDisable();
default void onDisable() {}
}

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.common.command;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import me.lucko.luckperms.common.command.utils.ArgumentTokenizer;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public abstract class BrigadierCommandExecutor<S> extends CommandManager implements Command<S>, SuggestionProvider<S> {
protected static final String[] COMMAND_ALIASES = new String[]{"luckperms", "lp", "perm", "perms", "permission", "permissions"};
private final LuckPermsPlugin plugin;
protected BrigadierCommandExecutor(LuckPermsPlugin plugin) {
super(plugin);
this.plugin = plugin;
}
public abstract Sender getSender(S source);
public abstract List<String> resolveSelectors(S source, List<String> args);
@Override
public int run(CommandContext<S> context) throws CommandSyntaxException {
S source = context.getSource();
Sender sender = getSender(source);
int start = context.getRange().getStart();
String buffer = context.getInput().substring(start);
List<String> arguments;
if (this.plugin.getConfiguration().get(ConfigKeys.RESOLVE_COMMAND_SELECTORS)) {
arguments = resolveSelectors(source, ArgumentTokenizer.EXECUTE.tokenizeInput(buffer));
} else {
arguments = ArgumentTokenizer.EXECUTE.tokenizeInput(buffer);
}
String label = arguments.remove(0);
if (label.startsWith("/")) {
label = label.substring(1);
}
executeCommand(sender, label, arguments);
return Command.SINGLE_SUCCESS;
}
@Override
public CompletableFuture<Suggestions> getSuggestions(CommandContext<S> context, SuggestionsBuilder builder) throws CommandSyntaxException {
S source = context.getSource();
Sender sender = getSender(source);
int idx = builder.getStart();
String buffer = builder.getInput().substring(idx);
idx += buffer.length();
List<String> arguments = ArgumentTokenizer.TAB_COMPLETE.tokenizeInput(buffer);
List<String> resolvedArguments;
if (this.plugin.getConfiguration().get(ConfigKeys.RESOLVE_COMMAND_SELECTORS)) {
resolvedArguments = resolveSelectors(source, new ArrayList<>(arguments));
} else {
resolvedArguments = arguments;
}
if (!arguments.isEmpty() && !resolvedArguments.isEmpty()) {
idx -= arguments.get(arguments.size() - 1).length();
}
List<String> completions = tabCompleteCommand(sender, resolvedArguments);
// Offset the builder from the current string range so suggestions are placed in the right spot
builder = builder.createOffset(idx);
for (String completion : completions) {
builder.suggest(completion);
}
return builder.buildFuture();
}
}

View File

@ -173,6 +173,7 @@ public enum CommandPermission {
public static final String ROOT = "luckperms.";
private final String node;
private final String permission;
private final Type type;
@ -180,14 +181,20 @@ public enum CommandPermission {
this.type = type;
if (type == Type.NONE) {
this.node = ROOT + node;
this.node = node;
} else {
this.node = ROOT + type.getTag() + "." + node;
this.node = type.getTag() + "." + node;
}
this.permission = ROOT + this.node;
}
public String getNode() {
return this.node;
}
public String getPermission() {
return this.node;
return this.permission;
}
public boolean isAuthorized(Sender sender) {

View File

@ -505,9 +505,9 @@ public final class ConfigKeys {
public static final ConfigKey<Boolean> VAULT_IGNORE_WORLD = booleanKey("vault-ignore-world", false);
/**
* If the owner of an integrated server should automatically bypasses all permission checks. On fabric, this only applies on an Integrated Server.
* If the owner of an integrated server should automatically bypass all permission checks. On fabric and forge, this only applies on an Integrated Server.
*/
public static final ConfigKey<Boolean> FABRIC_INTEGRATED_SERVER_OWNER_BYPASSES_CHECKS = booleanKey("integrated-server-owner-bypasses-checks", true);
public static final ConfigKey<Boolean> INTEGRATED_SERVER_OWNER_BYPASSES_CHECKS = booleanKey("integrated-server-owner-bypasses-checks", true);
/**
* Disabled context calculators

View File

@ -117,8 +117,12 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
this.dependencyManager = new DependencyManager(this);
this.dependencyManager.loadDependencies(getGlobalDependencies());
// load translations
this.translationManager = new TranslationManager(this);
this.translationManager.reload();
// load some utilities early
this.permissionRegistry = new PermissionRegistry(getBootstrap().getScheduler());
}
public final void enable() {
@ -130,7 +134,6 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
// load some utilities early
this.verboseHandler = new VerboseHandler(getBootstrap().getScheduler());
this.permissionRegistry = new PermissionRegistry(getBootstrap().getScheduler());
this.logDispatcher = new LogDispatcher(this);
// load configuration

View File

@ -135,10 +135,12 @@ public interface LuckPermsBootstrap {
/**
* Gets the plugins main data storage directory
*
* <p>Bukkit: /root/plugins/LuckPerms</p>
* <p>Bungee: /root/plugins/LuckPerms</p>
* <p>Sponge: /root/luckperms/</p>
* <p>Fabric: /root/mods/LuckPerms</p>
* <p>Bukkit: ./plugins/LuckPerms</p>
* <p>BungeeCord: ./plugins/LuckPerms</p>
* <p>Sponge: ./luckperms/</p>
* <p>Velocity: ./plugins/luckperms</p>
* <p>Fabric: ./mods/LuckPerms</p>
* <p>Forge: ./config/luckperms</p>
*
* @return the platforms data folder
*/

View File

@ -68,7 +68,7 @@ public class FabricCalculatorFactory implements CalculatorFactory {
}
boolean integratedOwner = queryOptions.option(FabricContextManager.INTEGRATED_SERVER_OWNER).orElse(false);
if (integratedOwner && this.plugin.getConfiguration().get(ConfigKeys.FABRIC_INTEGRATED_SERVER_OWNER_BYPASSES_CHECKS)) {
if (integratedOwner && this.plugin.getConfiguration().get(ConfigKeys.INTEGRATED_SERVER_OWNER_BYPASSES_CHECKS)) {
processors.add(ServerOwnerProcessor.INSTANCE);
}

View File

@ -25,21 +25,12 @@
package me.lucko.luckperms.fabric;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import com.mojang.brigadier.tree.ArgumentCommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import me.lucko.luckperms.common.command.CommandManager;
import me.lucko.luckperms.common.command.utils.ArgumentTokenizer;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.command.BrigadierCommandExecutor;
import me.lucko.luckperms.common.sender.Sender;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.minecraft.command.argument.EntityArgumentType;
import net.minecraft.server.command.ServerCommandSource;
@ -47,14 +38,12 @@ import net.minecraft.server.network.ServerPlayerEntity;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.CompletableFuture;
import static com.mojang.brigadier.arguments.StringArgumentType.greedyString;
import static net.minecraft.server.command.CommandManager.argument;
import static net.minecraft.server.command.CommandManager.literal;
public class FabricCommandExecutor extends CommandManager implements Command<ServerCommandSource>, SuggestionProvider<ServerCommandSource> {
private static final String[] COMMAND_ALIASES = new String[] {"luckperms", "lp", "perm", "perms", "permission", "permissions"};
public class FabricCommandExecutor extends BrigadierCommandExecutor<ServerCommandSource> {
private final LPFabricPlugin plugin;
@ -82,52 +71,12 @@ public class FabricCommandExecutor extends CommandManager implements Command<Ser
}
@Override
public int run(CommandContext<ServerCommandSource> ctx) {
ServerCommandSource source = ctx.getSource();
Sender wrapped = this.plugin.getSenderFactory().wrap(source);
int start = ctx.getRange().getStart();
List<String> arguments = resolveSelectors(source, ArgumentTokenizer.EXECUTE.tokenizeInput(ctx.getInput().substring(start)));
String label = arguments.remove(0);
if (label.startsWith("/")) {
label = label.substring(1);
}
executeCommand(wrapped, label, arguments);
return Command.SINGLE_SUCCESS;
public Sender getSender(ServerCommandSource source) {
return this.plugin.getSenderFactory().wrap(source);
}
@Override
public CompletableFuture<Suggestions> getSuggestions(CommandContext<ServerCommandSource> ctx, SuggestionsBuilder builder) {
ServerCommandSource source = ctx.getSource();
Sender wrapped = this.plugin.getSenderFactory().wrap(source);
int idx = builder.getStart();
String buffer = ctx.getInput().substring(idx);
idx += buffer.length();
List<String> arguments = resolveSelectors(source, ArgumentTokenizer.TAB_COMPLETE.tokenizeInput(buffer));
if (!arguments.isEmpty()) {
idx -= arguments.get(arguments.size() - 1).length();
}
List<String> completions = tabCompleteCommand(wrapped, arguments);
// Offset the builder from the current string range so suggestions are placed in the right spot
builder = builder.createOffset(idx);
for (String completion : completions) {
builder.suggest(completion);
}
return builder.buildFuture();
}
private List<String> resolveSelectors(ServerCommandSource source, List<String> args) {
if (!this.plugin.getConfiguration().get(ConfigKeys.RESOLVE_COMMAND_SELECTORS)) {
return args;
}
public List<String> resolveSelectors(ServerCommandSource source, List<String> args) {
// usage of @ selectors requires at least level 2 permission
ServerCommandSource atAllowedSource = source.hasPermissionLevel(2) ? source : source.withLevel(2);
for (ListIterator<String> it = args.listIterator(); it.hasNext(); ) {

75
forge/build.gradle Normal file
View File

@ -0,0 +1,75 @@
buildscript {
repositories {
maven { url 'https://plugins.gradle.org/m2' }
maven { url 'https://maven.minecraftforge.net/' }
}
dependencies {
classpath 'gradle.plugin.com.github.johnrengelman:shadow:7.1.2'
classpath 'net.kyori:blossom:1.3.0'
classpath 'net.minecraftforge.gradle:ForgeGradle:5.1.+'
}
}
apply plugin: 'com.github.johnrengelman.shadow'
apply plugin: 'net.kyori.blossom'
apply plugin: 'net.minecraftforge.gradle'
sourceCompatibility = 1.8
targetCompatibility = 17
blossom {
replaceTokenIn 'src/main/java/me/lucko/luckperms/forge/LPForgeBootstrap.java'
replaceToken '@version@', project.ext.fullVersion
}
minecraft {
mappings channel: 'official', version: minecraftVersion
}
configurations {
}
repositories {
}
dependencies {
minecraft "net.minecraftforge:forge:${minecraftVersion}-${forgeVersion}"
implementation project(':common')
compileOnly project(':common:loader-utils')
compileOnly project(':forge:forge-api')
}
reobf {
shadowJar {}
}
shadowJar {
archiveFileName = "luckperms-forge.jarinjar"
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 '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'
}
artifacts {
archives shadowJar
}

View File

@ -0,0 +1,29 @@
buildscript {
repositories {
maven { url 'https://maven.minecraftforge.net/' }
}
dependencies {
classpath 'net.minecraftforge.gradle:ForgeGradle:5.1.+'
}
}
apply plugin: 'net.minecraftforge.gradle'
sourceCompatibility = 1.8
targetCompatibility = 17
minecraft {
mappings channel: 'official', version: minecraftVersion
}
configurations {
}
repositories {
}
dependencies {
minecraft "net.minecraftforge:forge:${minecraftVersion}-${forgeVersion}"
implementation project(':api')
}

View File

@ -0,0 +1,85 @@
/*
* 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.forge.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.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.CapabilityManager;
import net.minecraftforge.common.capabilities.CapabilityToken;
/**
* A Forge {@link Capability} that attaches LuckPerms functionality onto {@link ServerPlayer}s.
*/
public interface UserCapability {
/**
* The identifier used for the capability
*/
ResourceLocation IDENTIFIER = new ResourceLocation("luckperms", "user");
/**
* The capability instance.
*/
Capability<UserCapability> CAPABILITY = CapabilityManager.get(new CapabilityToken<UserCapability>(){});
/**
* 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();
}

2
forge/gradle.properties Normal file
View File

@ -0,0 +1,2 @@
minecraftVersion=1.18.2
forgeVersion=40.1.20

78
forge/loader/build.gradle Normal file
View File

@ -0,0 +1,78 @@
buildscript {
repositories {
maven { url 'https://plugins.gradle.org/m2' }
maven { url 'https://maven.minecraftforge.net/' }
}
dependencies {
classpath 'gradle.plugin.com.github.johnrengelman:shadow:7.1.2'
classpath 'net.minecraftforge.gradle:ForgeGradle:5.1.+'
}
}
apply plugin: 'com.github.johnrengelman.shadow'
apply plugin: 'java-library'
apply plugin: 'net.minecraftforge.gradle'
sourceCompatibility = 1.8
targetCompatibility = 17
minecraft {
mappings channel: 'official', version: minecraftVersion
}
repositories {
}
dependencies {
minecraft "net.minecraftforge:forge:${minecraftVersion}-${forgeVersion}"
implementation project(':api')
implementation project(':common:loader-utils')
implementation project(':forge:forge-api')
}
build {
dependsOn(":forge:build")
dependsOn(":forge-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/mods.toml') {
expand 'version': project.ext.fullVersion
}
}
reobf {
shadowJar {}
}
shadowJar {
archiveFileName = "LuckPerms-Forge-${project.ext.fullVersion}.jar"
from {
project(':forge').tasks.shadowJar.archiveFile
}
dependencies {
include(dependency('net.luckperms:.*'))
include(dependency('me.lucko.luckperms:.*'))
}
}
artifacts {
archives shadowJar
}

View File

@ -0,0 +1,100 @@
/*
* 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.forge.loader;
import me.lucko.luckperms.common.loader.JarInJarClassLoader;
import me.lucko.luckperms.common.loader.LoaderBootstrap;
import net.minecraftforge.fml.IExtensionPoint;
import net.minecraftforge.fml.ModContainer;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.fml.loading.FMLEnvironment;
import net.minecraftforge.network.NetworkConstants;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.lang.reflect.InvocationTargetException;
import java.util.function.Supplier;
@Mod(value = "luckperms")
public class ForgeLoaderPlugin implements Supplier<ModContainer> {
private static final Logger LOGGER = LogManager.getLogger("luckperms");
private static final String JAR_NAME = "luckperms-forge.jarinjar";
private static final String BOOTSTRAP_CLASS = "me.lucko.luckperms.forge.LPForgeBootstrap";
private final ModContainer container;
private JarInJarClassLoader loader;
private LoaderBootstrap plugin;
public ForgeLoaderPlugin() {
this.container = ModList.get().getModContainerByObject(this).orElse(null);
markAsNotRequiredClientSide();
if (FMLEnvironment.dist.isClient()) {
LOGGER.info("Skipping LuckPerms init (not supported on the client!)");
return;
}
this.loader = new JarInJarClassLoader(getClass().getClassLoader(), JAR_NAME);
FMLJavaModLoadingContext.get().getModEventBus().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();
}
private static void markAsNotRequiredClientSide() {
try {
// workaround as we don't compile against java 17
ModLoadingContext.class.getDeclaredMethod("registerExtensionPoint", Class.class, Supplier.class)
.invoke(
ModLoadingContext.get(),
IExtensionPoint.DisplayTest.class,
(Supplier<?>) () -> new IExtensionPoint.DisplayTest(
() -> NetworkConstants.IGNORESERVERONLY,
(a, b) -> true
)
);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new IllegalStateException(e);
}
}
}

View File

@ -0,0 +1,20 @@
modLoader="javafml"
loaderVersion="[40,)"
license="MIT"
issueTrackerURL="https://github.com/LuckPerms/LuckPerms/issues"
[[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."
[[dependencies.luckperms]]
modId="forge"
mandatory=true
versionRange="[40.1.20,)"
ordering="NONE"
side="BOTH"

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

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

View File

@ -0,0 +1,110 @@
/*
* 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.forge;
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 net.minecraftforge.event.RegisterCommandsEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import java.util.List;
import java.util.ListIterator;
public class ForgeCommandExecutor extends BrigadierCommandExecutor<CommandSourceStack> {
private final LPForgePlugin plugin;
public ForgeCommandExecutor(LPForgePlugin 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,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.forge;
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 ForgeConfigAdapter extends ConfigurateConfigAdapter {
public ForgeConfigAdapter(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,50 @@
/*
* 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.forge;
import me.lucko.luckperms.common.api.LuckPermsApiProvider;
import me.lucko.luckperms.common.event.AbstractEventBus;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import net.minecraftforge.fml.ModContainer;
import net.minecraftforge.fml.ModList;
public class ForgeEventBus extends AbstractEventBus<ModContainer> {
public ForgeEventBus(LuckPermsPlugin plugin, LuckPermsApiProvider apiProvider) {
super(plugin, apiProvider);
}
@Override
protected ModContainer checkPlugin(Object mod) throws IllegalArgumentException {
ModContainer modContainer = ModList.get().getModContainerByObject(mod).orElse(null);
if (modContainer != null) {
return modContainer;
}
throw new IllegalArgumentException("Object " + mod + " (" + mod.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.forge;
import me.lucko.luckperms.common.plugin.scheduler.AbstractJavaScheduler;
import java.util.concurrent.Executor;
public class ForgeSchedulerAdapter extends AbstractJavaScheduler {
private final Executor sync;
public ForgeSchedulerAdapter(LPForgeBootstrap 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,116 @@
/*
* 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.forge;
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.forge.capabilities.UserCapability;
import me.lucko.luckperms.forge.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.CommandSourceStack;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import java.util.Locale;
import java.util.UUID;
public class ForgeSenderFactory extends SenderFactory<LPForgePlugin, CommandSourceStack> {
public ForgeSenderFactory(LPForgePlugin 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) {
sender.getServer().getCommands().performCommand(sender, command);
}
@Override
protected boolean isConsole(CommandSourceStack sender) {
return !(sender.getEntity() instanceof Player);
}
public static net.minecraft.network.chat.Component toNativeText(Component component) {
return net.minecraft.network.chat.Component.Serializer.fromJson(GsonComponentSerializer.gson().serialize(component));
}
}

View File

@ -0,0 +1,290 @@
/*
* 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.forge;
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.forge.util.ForgeEventBusFacade;
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.minecraftforge.event.server.ServerAboutToStartEvent;
import net.minecraftforge.event.server.ServerStoppingEvent;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.ModContainer;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.loading.FMLPaths;
import net.minecraftforge.forgespi.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 LPForgeBootstrap 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 ForgeEventBusFacade forgeEventBus;
/**
* The plugin instance
*/
private final LPForgePlugin 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 LPForgeBootstrap(Supplier<ModContainer> loader) {
this.loader = loader;
this.logger = new Log4jPluginLogger(LogManager.getLogger(LPForgeBootstrap.ID));
this.schedulerAdapter = new ForgeSchedulerAdapter(this);
this.classPathAppender = new JarInJarClassPathAppender(getClass().getClassLoader());
this.forgeEventBus = new ForgeEventBusFacade();
this.plugin = new LPForgePlugin(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.FORGE;
}
@Override
public String getServerBrand() {
return ModList.get().getModContainerById("forge")
.map(ModContainer::getModInfo)
.map(IModInfo::getDisplayName)
.orElse("null");
}
@Override
public String getServerVersion() {
String forgeVersion = ModList.get().getModContainerById("forge")
.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(LPForgeBootstrap.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,249 @@
/*
* 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.forge;
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.forge.calculator.ForgeCalculatorFactory;
import me.lucko.luckperms.forge.capabilities.UserCapabilityListener;
import me.lucko.luckperms.forge.context.ForgeContextManager;
import me.lucko.luckperms.forge.context.ForgePlayerCalculator;
import me.lucko.luckperms.forge.listeners.ForgeAutoOpListener;
import me.lucko.luckperms.forge.listeners.ForgeCommandListUpdater;
import me.lucko.luckperms.forge.listeners.ForgeConnectionListener;
import me.lucko.luckperms.forge.listeners.ForgePlatformListener;
import me.lucko.luckperms.forge.messaging.ForgeMessagingFactory;
import me.lucko.luckperms.forge.service.ForgePermissionHandlerListener;
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.minecraftforge.fml.ModContainer;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
/**
* LuckPerms implementation for Forge.
*/
public class LPForgePlugin extends AbstractLuckPermsPlugin {
private final LPForgeBootstrap bootstrap;
private ForgeSenderFactory senderFactory;
private ForgeConnectionListener connectionListener;
private ForgeCommandExecutor commandManager;
private StandardUserManager userManager;
private StandardGroupManager groupManager;
private StandardTrackManager trackManager;
private ForgeContextManager contextManager;
public LPForgePlugin(LPForgeBootstrap bootstrap) {
this.bootstrap = bootstrap;
}
@Override
public LPForgeBootstrap getBootstrap() {
return this.bootstrap;
}
protected void registerEarlyListeners() {
this.connectionListener = new ForgeConnectionListener(this);
this.bootstrap.registerListeners(this.connectionListener);
ForgePlatformListener platformListener = new ForgePlatformListener(this);
this.bootstrap.registerListeners(platformListener);
UserCapabilityListener userCapabilityListener = new UserCapabilityListener();
this.bootstrap.registerListeners(userCapabilityListener);
ForgePermissionHandlerListener permissionHandlerListener = new ForgePermissionHandlerListener(this);
this.bootstrap.registerListeners(permissionHandlerListener);
this.commandManager = new ForgeCommandExecutor(this);
this.bootstrap.registerListeners(this.commandManager);
}
@Override
protected void setupSenderFactory() {
this.senderFactory = new ForgeSenderFactory(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 ForgeConfigAdapter(this, resolveConfig("luckperms.conf"));
}
@Override
protected void registerPlatformListeners() {
// Too late for Forge, registered in #registerEarlyListeners
}
@Override
protected MessagingFactory<?> provideMessagingFactory() {
return new ForgeMessagingFactory(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 ForgeCalculatorFactory(this);
}
@Override
protected void setupContextManager() {
this.contextManager = new ForgeContextManager(this);
ForgePlayerCalculator playerCalculator = new ForgePlayerCalculator(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 ForgeEventBus(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 ForgeAutoOpListener(this));
}
// register forge command list updater
if (getConfiguration().get(ConfigKeys.UPDATE_CLIENT_COMMAND_LIST)) {
getApiProvider().getEventBus().subscribe(new ForgeCommandListUpdater(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) {
LPForgePlugin.this.bootstrap.getPluginLogger().info(PlainTextComponentSerializer.plainText().serialize(TranslationManager.render(message)));
}
});
}
public ForgeSenderFactory getSenderFactory() {
return this.senderFactory;
}
@Override
public ForgeConnectionListener getConnectionListener() {
return this.connectionListener;
}
@Override
public ForgeCommandExecutor 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 ForgeContextManager getContextManager() {
return this.contextManager;
}
}

View File

@ -0,0 +1,78 @@
/*
* 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.forge.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.forge.LPForgePlugin;
import me.lucko.luckperms.forge.context.ForgeContextManager;
import net.luckperms.api.query.QueryOptions;
import java.util.ArrayList;
import java.util.List;
public class ForgeCalculatorFactory implements CalculatorFactory {
private final LPForgePlugin plugin;
public ForgeCalculatorFactory(LPForgePlugin 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(ForgeContextManager.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,52 @@
/*
* 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.forge.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,149 @@
/*
* 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.forge.capabilities;
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.forge.context.ForgeContextManager;
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 {
/**
* 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) player.getCapability(CAPABILITY)
.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) player.getCapability(CAPABILITY).resolve().orElse(null);
}
private boolean initialised = 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, ForgeContextManager 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");
}
}
@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,99 @@
/*
* 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.forge.capabilities;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class UserCapabilityListener {
@SubscribeEvent
public void onRegisterCapabilities(RegisterCapabilitiesEvent event) {
event.register(UserCapabilityImpl.class);
}
@SubscribeEvent
public void onAttachCapabilities(AttachCapabilitiesEvent<Entity> event) {
if (!(event.getObject() instanceof ServerPlayer)) {
return;
}
event.addCapability(UserCapability.IDENTIFIER, new UserCapabilityProvider(new UserCapabilityImpl()));
}
@SubscribeEvent
public void onPlayerClone(PlayerEvent.Clone event) {
if (!event.isWasDeath()) {
return;
}
Player previousPlayer = event.getOriginal();
Player currentPlayer = event.getPlayer();
previousPlayer.reviveCaps();
try {
UserCapabilityImpl previous = UserCapabilityImpl.get(previousPlayer);
UserCapabilityImpl current = UserCapabilityImpl.get(currentPlayer);
current.initialise(previous);
current.getQueryOptionsCache().invalidate();
} finally {
previousPlayer.invalidateCaps();
}
}
private static final class UserCapabilityProvider implements ICapabilityProvider {
private final UserCapabilityImpl userCapability;
private UserCapabilityProvider(UserCapabilityImpl userCapability) {
this.userCapability = userCapability;
}
@SuppressWarnings("unchecked")
@NotNull
@Override
public <T> LazyOptional<T> getCapability(@NotNull Capability<T> cap, @Nullable Direction side) {
if (cap != UserCapability.CAPABILITY) {
return LazyOptional.empty();
}
return LazyOptional.of(() -> (T) this.userCapability);
}
}
}

View File

@ -0,0 +1,80 @@
/*
* 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.forge.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.forge.LPForgePlugin;
import me.lucko.luckperms.forge.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 ForgeContextManager extends ContextManager<ServerPlayer, ServerPlayer> {
public static final OptionKey<Boolean> INTEGRATED_SERVER_OWNER = OptionKey.of("integrated_server_owner", Boolean.class);
public ForgeContextManager(LPForgePlugin 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,149 @@
/*
* 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.forge.context;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.ImmutableContextSetImpl;
import me.lucko.luckperms.forge.LPForgePlugin;
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.Registry;
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.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Set;
public class ForgePlayerCalculator implements ContextCalculator<ServerPlayer> {
/**
* GameType.NOT_SET(-1, "") was removed in 1.17
*/
private static final int GAME_MODE_NOT_SET = -1;
private final LPForgePlugin plugin;
private final boolean gamemode;
private final boolean world;
private final boolean dimensionType;
public ForgePlayerCalculator(LPForgePlugin 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.getLevel();
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(Registry.DIMENSION_TYPE_REGISTRY).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.getPlayer());
}
@SubscribeEvent
public void onPlayerChangeGameMode(PlayerEvent.PlayerChangeGameModeEvent event) {
if (!this.gamemode || event.getNewGameMode().getId() == GAME_MODE_NOT_SET) {
return;
}
this.plugin.getContextManager().signalContextUpdate((ServerPlayer) event.getPlayer());
}
}

View File

@ -0,0 +1,98 @@
/*
* 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.forge.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.forge.LPForgePlugin;
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 ForgeAutoOpListener implements LuckPermsEventListener {
private static final String NODE = "luckperms.autoop";
private final LPForgePlugin plugin;
public ForgeAutoOpListener(LPForgePlugin 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,121 @@
/*
* 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.forge.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.forge.LPForgePlugin;
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 ForgeCommandListUpdater implements LuckPermsEventListener {
private final LPForgePlugin plugin;
private final LoadingCache<UUID, SendBuffer> sendingBuffers = CaffeineFactory.newBuilder()
.expireAfterAccess(10, TimeUnit.SECONDS)
.build(SendBuffer::new);
public ForgeCommandListUpdater(LPForgePlugin 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, ForgeCommandListUpdater.this.plugin.getBootstrap().getScheduler());
this.uniqueId = uniqueId;
}
@Override
protected Void perform() {
sendUpdate(this.uniqueId);
return null;
}
}
}

View File

@ -0,0 +1,154 @@
/*
* 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.forge.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.forge.ForgeSenderFactory;
import me.lucko.luckperms.forge.LPForgePlugin;
import me.lucko.luckperms.forge.capabilities.UserCapabilityImpl;
import net.kyori.adventure.text.Component;
import net.minecraft.Util;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.ChatType;
import net.minecraft.network.protocol.game.ClientboundChatPacket;
import net.minecraft.network.protocol.login.ClientboundLoginDisconnectPacket;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.event.entity.player.PlayerNegotiationEvent;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class ForgeConnectionListener extends AbstractConnectionListener {
private final LPForgePlugin plugin;
public ForgeConnectionListener(LPForgePlugin plugin) {
super(plugin);
this.plugin = plugin;
}
@SubscribeEvent
public void onPlayerNegotiation(PlayerNegotiationEvent event) {
String username = event.getProfile().getName();
UUID uniqueId = event.getProfile().isComplete() ? event.getProfile().getId() : Player.createPlayerUUID(username);
if (this.plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) {
this.plugin.getLogger().info("Processing pre-login (sync phase) for " + uniqueId + " - " + username);
}
event.enqueueWork(CompletableFuture.runAsync(() -> {
onPlayerNegotiationAsync(event.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(ForgeSenderFactory.toNativeText(component)));
connection.disconnect(ForgeSenderFactory.toNativeText(component));
} else {
// Schedule the message to be sent on the next tick.
this.plugin.getBootstrap().getServer().orElseThrow(IllegalStateException::new).execute(() -> {
Component component = TranslationManager.render(Message.LOADING_STATE_ERROR.build());
connection.send(new ClientboundChatPacket(ForgeSenderFactory.toNativeText(component), ChatType.SYSTEM, Util.NIL_UUID));
});
}
this.plugin.getEventDispatcher().dispatchPlayerLoginProcess(uniqueId, username, null);
}
}
@SubscribeEvent(priority = EventPriority.HIGHEST)
public void onPlayerLoadFromFile(PlayerEvent.LoadFromFile event) {
ServerPlayer player = (ServerPlayer) event.getPlayer();
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(ForgeSenderFactory.toNativeText(component));
} else {
player.sendMessage(ForgeSenderFactory.toNativeText(component), Util.NIL_UUID);
}
}
// 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.getPlayer();
handleDisconnect(player.getGameProfile().getId());
}
}

View File

@ -0,0 +1,94 @@
/*
* 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.forge.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.forge.LPForgePlugin;
import me.lucko.luckperms.forge.util.BrigadierInjector;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.server.players.ServerOpList;
import net.minecraftforge.event.AddReloadListenerEvent;
import net.minecraftforge.event.CommandEvent;
import net.minecraftforge.event.server.ServerStartedEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import java.io.IOException;
import java.util.Locale;
public class ForgePlatformListener {
private final LPForgePlugin plugin;
public ForgePlatformListener(LPForgePlugin 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,72 @@
/*
* 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.forge.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.forge.LPForgePlugin;
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 ForgeMessagingFactory extends MessagingFactory<LPForgePlugin> {
public ForgeMessagingFactory(LPForgePlugin 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,101 @@
/*
* 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.forge.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.forge.LPForgePlugin;
import net.luckperms.api.messenger.IncomingMessageConsumer;
import net.luckperms.api.messenger.Messenger;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.players.PlayerList;
import net.minecraftforge.network.NetworkRegistry;
import net.minecraftforge.network.event.EventNetworkChannel;
import io.netty.buffer.Unpooled;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class PluginMessageMessenger extends AbstractPluginMessageMessenger implements Messenger {
private static final ResourceLocation CHANNEL = new ResourceLocation(AbstractPluginMessageMessenger.CHANNEL);
private final LPForgePlugin plugin;
private EventNetworkChannel channel;
public PluginMessageMessenger(LPForgePlugin plugin, IncomingMessageConsumer consumer) {
super(consumer);
this.plugin = plugin;
}
public void init() {
this.channel = NetworkRegistry.newEventChannel(CHANNEL, () -> "1", predicate -> true, predicate -> true);
this.channel.addListener(event -> {
byte[] buf = new byte[event.getPayload().readableBytes()];
event.getPayload().readBytes(buf);
handleIncomingMessage(buf);
event.getSource().get().setPacketHandled(true);
});
}
@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;
}
FriendlyByteBuf byteBuf = new FriendlyByteBuf(Unpooled.buffer());
byteBuf.writeBytes(buf);
Packet<?> packet = new ClientboundCustomPayloadPacket(CHANNEL, byteBuf);
player.connection.send(packet);
SchedulerTask t = taskRef.getAndSet(null);
if (t != null) {
t.cancel();
}
}, 10, TimeUnit.SECONDS);
taskRef.set(task);
}
}

View File

@ -0,0 +1,155 @@
/*
* 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.forge.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.forge.LPForgeBootstrap;
import me.lucko.luckperms.forge.LPForgePlugin;
import me.lucko.luckperms.forge.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 net.minecraftforge.server.permission.handler.IPermissionHandler;
import net.minecraftforge.server.permission.nodes.PermissionDynamicContext;
import net.minecraftforge.server.permission.nodes.PermissionNode;
import net.minecraftforge.server.permission.nodes.PermissionTypes;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class ForgePermissionHandler implements IPermissionHandler {
public static final ResourceLocation IDENTIFIER = new ResourceLocation(LPForgeBootstrap.ID, "permission_handler");
private final LPForgePlugin plugin;
private final Set<PermissionNode<?>> permissionNodes;
public ForgePermissionHandler(LPForgePlugin 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);
if (node.getType() == PermissionTypes.BOOLEAN) {
PermissionCache cache = user.getCachedData().getPermissionData(queryOptions);
Tristate value = cache.checkPermission(node.getNodeName(), CheckOrigin.PLATFORM_API_HAS_PERMISSION).result();
return (T) (Boolean) value.asBoolean();
}
if (node.getType() == PermissionTypes.INTEGER) {
MetaCache cache = user.getCachedData().getMetaData(queryOptions);
Integer value = cache.getMetaValue(node.getNodeName(), Integer::parseInt).orElse(null);
if (value != null) {
return (T) value;
}
}
if (node.getType() == PermissionTypes.STRING) {
MetaCache cache = user.getCachedData().getMetaData(queryOptions);
String value = cache.getMetaValue(node.getNodeName());
if (value != null) {
return (T) value;
}
}
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,65 @@
/*
* 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.forge.service;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.forge.LPForgePlugin;
import net.minecraftforge.common.ForgeConfig;
import net.minecraftforge.common.ForgeConfigSpec;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.server.permission.events.PermissionGatherEvent;
import net.minecraftforge.server.permission.handler.DefaultPermissionHandler;
import net.minecraftforge.server.permission.nodes.PermissionNode;
import net.minecraftforge.server.permission.nodes.PermissionTypes;
public class ForgePermissionHandlerListener {
private final LPForgePlugin plugin;
public ForgePermissionHandlerListener(LPForgePlugin plugin) {
this.plugin = plugin;
}
@SubscribeEvent
public void onPermissionGatherHandler(PermissionGatherEvent.Handler event) {
// Override the default permission handler with LuckPerms
ForgeConfigSpec.ConfigValue<String> permissionHandler = ForgeConfig.SERVER.permissionHandler;
if (permissionHandler.get().equals(DefaultPermissionHandler.IDENTIFIER.toString())) {
permissionHandler.set(ForgePermissionHandler.IDENTIFIER.toString());
}
event.addPermissionHandler(ForgePermissionHandler.IDENTIFIER, permissions -> new ForgePermissionHandler(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,186 @@
/*
* 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.forge.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.plugin.LuckPermsPlugin;
import me.lucko.luckperms.forge.capabilities.UserCapability;
import me.lucko.luckperms.forge.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(LuckPermsPlugin 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(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 String permission;
private final Predicate<CommandSourceStack> delegate;
private InjectedPermissionRequirement(String permission, Predicate<CommandSourceStack> delegate) {
this.permission = permission;
this.delegate = delegate;
}
@Override
public boolean test(CommandSourceStack source) {
if (source.getEntity() instanceof ServerPlayer) {
ServerPlayer player = (ServerPlayer) source.getEntity();
UserCapability user = UserCapabilityImpl.get(player);
Tristate 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,265 @@
/*
* 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.forge.util;
import me.lucko.luckperms.common.loader.JarInJarClassLoader;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.eventbus.api.GenericEvent;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.eventbus.api.IGenericEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
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;
/**
* 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 ForgeEventBusFacade {
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 = FMLJavaModLoadingContext.get().getModEventBus();
} else {
eventBus = MinecraftForge.EVENT_BUS;
}
if (IGenericEvent.class.isAssignableFrom(type.eventType)) {
addGenericListener(eventBus, type.genericType, subscribeEvent, type.eventType, invoker);
} else {
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 'genericType' is set if 'eventType' is a generic event
if (GenericEvent.class.isAssignableFrom(eventType) && genericType == null) {
throw new IllegalArgumentException(""
+ "Method " + method + " has @SubscribeEvent annotation, "
+ "but the generic argument type cannot be determined for "
+ "for the GenericEvent subtype: " + eventType
);
}
// 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#addGenericListener}.
*/
@SuppressWarnings("unchecked")
private static <T extends GenericEvent<? extends F>, F> void addGenericListener(IEventBus eventBus, Class<?> genericClassFilter, SubscribeEvent annotation, Class<?> eventType, Consumer<?> consumer) {
eventBus.addGenericListener((Class<F>) genericClassFilter, annotation.priority(), annotation.receiveCanceled(), (Class<T>) eventType, (Consumer<T>) consumer);
}
/**
* 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,640 @@
####################################################################################################
# +----------------------------------------------------------------------------------------------+ #
# | __ __ ___ __ __ | #
# | | | | / ` |__/ |__) |__ |__) |\/| /__` | #
# | |___ \__/ \__, | \ | |___ | \ | | .__/ | #
# | | #
# | https://luckperms.net | #
# | | #
# | WIKI: https://luckperms.net/wiki | #
# | DISCORD: https://discord.gg/luckperms | #
# | BUG REPORTS: https://github.com/lucko/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
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.gamepedia.com/Commands#Target_selectors
resolve-command-selectors = false

View File

@ -1,2 +1,4 @@
# Fabric requires some more ram.
org.gradle.jvmargs=-Xmx1G
org.gradle.jvmargs=-Xmx1G
# ForgeGradle is special.
org.gradle.daemon=false

View File

@ -21,6 +21,9 @@ include (
'bungee',
'bungee:loader',
'fabric',
'forge',
'forge:loader',
'forge:forge-api',
'nukkit',
'nukkit:loader',
'sponge',