From 642f1d30192a514528b867186173c0f0eff2fe89 Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Mon, 12 Aug 2024 11:11:27 -0700 Subject: [PATCH] Port to NeoForge (#3950) --- .gitattributes | 5 + .../net/luckperms/api/platform/Platform.java | 1 + gradle/libs.versions.toml | 2 + gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 43504 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 7 +- gradlew.bat | 2 + neoforge/build.gradle | 63 ++ neoforge/gradle.properties | 2 + neoforge/loader/build.gradle | 93 +++ .../neoforge/loader/NeoForgeLoaderPlugin.java | 74 ++ .../resources/META-INF/neoforge.mods.toml | 16 + .../loader/src/main/resources/luckperms.png | Bin 0 -> 36936 bytes .../loader/src/main/resources/pack.mcmeta | 6 + neoforge/neoforge-api/build.gradle | 16 + .../neoforge/capabilities/UserCapability.java | 83 +++ .../neoforge/LPNeoForgeBootstrap.java | 287 ++++++++ .../luckperms/neoforge/LPNeoForgePlugin.java | 251 +++++++ .../neoforge/NeoForgeCommandExecutor.java | 108 +++ .../neoforge/NeoForgeConfigAdapter.java | 46 ++ .../luckperms/neoforge/NeoForgeEventBus.java | 47 ++ .../neoforge/NeoForgeSchedulerAdapter.java | 45 ++ .../neoforge/NeoForgeSenderFactory.java | 124 ++++ .../calculator/NeoForgeCalculatorFactory.java | 77 +++ .../calculator/ServerOwnerProcessor.java | 51 ++ .../capabilities/UserCapabilityImpl.java | 163 +++++ .../capabilities/UserCapabilityListener.java | 69 ++ .../context/NeoForgeContextManager.java | 79 +++ .../context/NeoForgePlayerCalculator.java | 147 ++++ .../listeners/NeoForgeAutoOpListener.java | 97 +++ .../listeners/NeoForgeCommandListUpdater.java | 119 ++++ .../listeners/NeoForgeConnectionListener.java | 158 +++++ .../listeners/NeoForgePlatformListener.java | 92 +++ .../messaging/NeoForgeMessagingFactory.java | 70 ++ .../messaging/PluginMessageMessenger.java | 114 ++++ .../service/NeoForgePermissionHandler.java | 166 +++++ .../NeoForgePermissionHandlerListener.java | 64 ++ .../neoforge/util/AsyncConfigurationTask.java | 61 ++ .../neoforge/util/BrigadierInjector.java | 197 ++++++ .../neoforge/util/NeoForgeEventBusFacade.java | 241 +++++++ neoforge/src/main/resources/luckperms.conf | 641 ++++++++++++++++++ settings.gradle | 5 +- 42 files changed, 3887 insertions(+), 4 deletions(-) create mode 100644 .gitattributes create mode 100644 neoforge/build.gradle create mode 100644 neoforge/gradle.properties create mode 100644 neoforge/loader/build.gradle create mode 100644 neoforge/loader/src/main/java/me/lucko/luckperms/neoforge/loader/NeoForgeLoaderPlugin.java create mode 100644 neoforge/loader/src/main/resources/META-INF/neoforge.mods.toml create mode 100644 neoforge/loader/src/main/resources/luckperms.png create mode 100644 neoforge/loader/src/main/resources/pack.mcmeta create mode 100644 neoforge/neoforge-api/build.gradle create mode 100644 neoforge/neoforge-api/src/main/java/me/lucko/luckperms/neoforge/capabilities/UserCapability.java create mode 100644 neoforge/src/main/java/me/lucko/luckperms/neoforge/LPNeoForgeBootstrap.java create mode 100644 neoforge/src/main/java/me/lucko/luckperms/neoforge/LPNeoForgePlugin.java create mode 100644 neoforge/src/main/java/me/lucko/luckperms/neoforge/NeoForgeCommandExecutor.java create mode 100644 neoforge/src/main/java/me/lucko/luckperms/neoforge/NeoForgeConfigAdapter.java create mode 100644 neoforge/src/main/java/me/lucko/luckperms/neoforge/NeoForgeEventBus.java create mode 100644 neoforge/src/main/java/me/lucko/luckperms/neoforge/NeoForgeSchedulerAdapter.java create mode 100644 neoforge/src/main/java/me/lucko/luckperms/neoforge/NeoForgeSenderFactory.java create mode 100644 neoforge/src/main/java/me/lucko/luckperms/neoforge/calculator/NeoForgeCalculatorFactory.java create mode 100644 neoforge/src/main/java/me/lucko/luckperms/neoforge/calculator/ServerOwnerProcessor.java create mode 100644 neoforge/src/main/java/me/lucko/luckperms/neoforge/capabilities/UserCapabilityImpl.java create mode 100644 neoforge/src/main/java/me/lucko/luckperms/neoforge/capabilities/UserCapabilityListener.java create mode 100644 neoforge/src/main/java/me/lucko/luckperms/neoforge/context/NeoForgeContextManager.java create mode 100644 neoforge/src/main/java/me/lucko/luckperms/neoforge/context/NeoForgePlayerCalculator.java create mode 100644 neoforge/src/main/java/me/lucko/luckperms/neoforge/listeners/NeoForgeAutoOpListener.java create mode 100644 neoforge/src/main/java/me/lucko/luckperms/neoforge/listeners/NeoForgeCommandListUpdater.java create mode 100644 neoforge/src/main/java/me/lucko/luckperms/neoforge/listeners/NeoForgeConnectionListener.java create mode 100644 neoforge/src/main/java/me/lucko/luckperms/neoforge/listeners/NeoForgePlatformListener.java create mode 100644 neoforge/src/main/java/me/lucko/luckperms/neoforge/messaging/NeoForgeMessagingFactory.java create mode 100644 neoforge/src/main/java/me/lucko/luckperms/neoforge/messaging/PluginMessageMessenger.java create mode 100644 neoforge/src/main/java/me/lucko/luckperms/neoforge/service/NeoForgePermissionHandler.java create mode 100644 neoforge/src/main/java/me/lucko/luckperms/neoforge/service/NeoForgePermissionHandlerListener.java create mode 100644 neoforge/src/main/java/me/lucko/luckperms/neoforge/util/AsyncConfigurationTask.java create mode 100644 neoforge/src/main/java/me/lucko/luckperms/neoforge/util/BrigadierInjector.java create mode 100644 neoforge/src/main/java/me/lucko/luckperms/neoforge/util/NeoForgeEventBusFacade.java create mode 100644 neoforge/src/main/resources/luckperms.conf diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..022b84144 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# These are explicitly windows files and should use crlf +*.bat text eol=crlf diff --git a/api/src/main/java/net/luckperms/api/platform/Platform.java b/api/src/main/java/net/luckperms/api/platform/Platform.java index bc31a34a8..32b727316 100644 --- a/api/src/main/java/net/luckperms/api/platform/Platform.java +++ b/api/src/main/java/net/luckperms/api/platform/Platform.java @@ -76,6 +76,7 @@ public interface Platform { NUKKIT("Nukkit"), VELOCITY("Velocity"), FABRIC("Fabric"), + NEOFORGE("NeoForge"), FORGE("Forge"), STANDALONE("Standalone"); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0d28dee09..f9d402340 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,7 @@ [versions] shadow = "8.1.7" blossom = "1.3.1" +moddevgradle = "1.0.17" forgegradle = "[6.0,6.2)" loom = "1.6-SNAPSHOT" licenser = "0.6.1" @@ -8,6 +9,7 @@ licenser = "0.6.1" [plugins] blossom = { id = "net.kyori.blossom", version.ref = "blossom" } shadow = { id = "io.github.goooler.shadow", version.ref = "shadow" } +moddevgradle = { id = "net.neoforged.moddev", version.ref = "moddevgradle" } forgegradle = { id = "net.minecraftforge.gradle", version.ref = "forgegradle" } loom = { id = "fabric-loom", version.ref = "loom" } licenser = { id = "org.cadixdev.licenser", version.ref = "licenser" } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f3d4ba8a0da8d277868979cfbc8ad796..2c3521197d7c4586c843d1d3e9090525f1898cde 100644 GIT binary patch delta 8703 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 Ojj7^59?Sdh$^Qpp*~F>< delta 8662 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b82aa23a4..09523c0e5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a426..f5feea6d6 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 25da30dbd..9d21a2183 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## diff --git a/neoforge/build.gradle b/neoforge/build.gradle new file mode 100644 index 000000000..8e1adb360 --- /dev/null +++ b/neoforge/build.gradle @@ -0,0 +1,63 @@ +plugins { + alias(libs.plugins.blossom) + alias(libs.plugins.shadow) + alias(libs.plugins.moddevgradle) +} + +sourceCompatibility = 17 +targetCompatibility = 21 + +Configuration shade = configurations.create('shade') +configurations.implementation { + extendsFrom configurations.shade +} + +blossom { + replaceTokenIn 'src/main/java/me/lucko/luckperms/neoforge/LPNeoForgeBootstrap.java' + replaceToken '@version@', project.ext.fullVersion +} + +neoForge { + version = project.neoForgeVersion + + validateAccessTransformers = true +} + +dependencies { + add('shade', project(':common')) + compileOnly project(':common:loader-utils') + compileOnly project(':neoforge:neoforge-api') +} + +shadowJar { + archiveFileName = "luckperms-neoforge.jarinjar" + configurations = [shade] + + dependencies { + include(dependency('me.lucko.luckperms:.*')) + } + + relocate 'net.kyori.adventure', 'me.lucko.luckperms.lib.adventure' + relocate 'net.kyori.event', 'me.lucko.luckperms.lib.eventbus' + relocate 'com.github.benmanes.caffeine', 'me.lucko.luckperms.lib.caffeine' + relocate 'okio', 'me.lucko.luckperms.lib.okio' + relocate 'okhttp3', 'me.lucko.luckperms.lib.okhttp3' + relocate 'net.bytebuddy', 'me.lucko.luckperms.lib.bytebuddy' + relocate 'me.lucko.commodore', 'me.lucko.luckperms.lib.commodore' + relocate 'org.mariadb.jdbc', 'me.lucko.luckperms.lib.mariadb' + relocate 'com.mysql', 'me.lucko.luckperms.lib.mysql' + relocate 'org.postgresql', 'me.lucko.luckperms.lib.postgresql' + relocate 'com.zaxxer.hikari', 'me.lucko.luckperms.lib.hikari' + relocate 'com.mongodb', 'me.lucko.luckperms.lib.mongodb' + relocate 'org.bson', 'me.lucko.luckperms.lib.bson' + relocate 'redis.clients.jedis', 'me.lucko.luckperms.lib.jedis' + relocate 'io.nats.client', 'me.lucko.luckperms.lib.nats' + relocate 'com.rabbitmq', 'me.lucko.luckperms.lib.rabbitmq' + relocate 'org.apache.commons.pool2', 'me.lucko.luckperms.lib.commonspool2' + relocate 'ninja.leaping.configurate', 'me.lucko.luckperms.lib.configurate' + relocate 'org.yaml.snakeyaml', 'me.lucko.luckperms.lib.yaml' +} + +artifacts { + archives shadowJar +} diff --git a/neoforge/gradle.properties b/neoforge/gradle.properties new file mode 100644 index 000000000..912a267b6 --- /dev/null +++ b/neoforge/gradle.properties @@ -0,0 +1,2 @@ +minecraftVersion=1.21 +neoForgeVersion=21.0.161 \ No newline at end of file diff --git a/neoforge/loader/build.gradle b/neoforge/loader/build.gradle new file mode 100644 index 000000000..38dbed6b0 --- /dev/null +++ b/neoforge/loader/build.gradle @@ -0,0 +1,93 @@ +import java.nio.file.Files +import java.nio.file.StandardCopyOption +import net.neoforged.moddevgradle.internal.RunGameTask + +plugins { + alias(libs.plugins.shadow) + alias(libs.plugins.moddevgradle) + id("java-library") +} + +sourceCompatibility = 17 +targetCompatibility = 21 + +neoForge { + version = project.neoForgeVersion + + validateAccessTransformers = true + + runs { + client { + client() + mods.set(new HashSet()) // Work around classpath issues by using the production jar for dev runs + } + server { + server() + mods.set(new HashSet()) // Work around classpath issues by using the production jar for dev runs + } + } +} + +// Work around classpath issues by using the production jar for dev runs +tasks.withType(RunGameTask).configureEach { + dependsOn(tasks.shadowJar) + doFirst { + File jar = file("run/mods/main.jar") + jar.parentFile.mkdirs() + Files.copy(tasks.shadowJar.archiveFile.get().asFile.toPath(), jar.toPath(), StandardCopyOption.REPLACE_EXISTING) + } +} + +Configuration shade = configurations.create('shade') +configurations.implementation { + extendsFrom configurations.shade +} + +dependencies { + add('shade', project(':api')) + add('shade', project(':common:loader-utils')) + add('shade', project(':neoforge:neoforge-api')) +} + +build { + dependsOn(":neoforge:build") + dependsOn(":neoforge:neoforge-api:build") +} + +jar { + manifest { + attributes( + 'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), + 'Implementation-Title': 'LuckPerms', + 'Implementation-Vendor': 'LuckPerms', + 'Implementation-Version': project.ext.fullVersion, + 'Specification-Title': 'luckperms', + 'Specification-Vendor': 'LuckPerms', + 'Specification-Version': '1' + ) + } +} + +processResources { + filesMatching('META-INF/neoforge.mods.toml') { + expand 'version': project.ext.fullVersion + } +} + +shadowJar { + archiveFileName = "LuckPerms-NeoForge-${project.ext.fullVersion}.jar" + configurations = [shade] + + from { + project(':neoforge').tasks.shadowJar.archiveFile + } + + dependencies { + include(dependency('net.luckperms:.*')) + include(dependency('me.lucko.luckperms:.*')) + } +} + +artifacts { + archives shadowJar +} diff --git a/neoforge/loader/src/main/java/me/lucko/luckperms/neoforge/loader/NeoForgeLoaderPlugin.java b/neoforge/loader/src/main/java/me/lucko/luckperms/neoforge/loader/NeoForgeLoaderPlugin.java new file mode 100644 index 000000000..bd838191a --- /dev/null +++ b/neoforge/loader/src/main/java/me/lucko/luckperms/neoforge/loader/NeoForgeLoaderPlugin.java @@ -0,0 +1,74 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge.loader; + +import me.lucko.luckperms.common.loader.JarInJarClassLoader; +import me.lucko.luckperms.common.loader.LoaderBootstrap; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.ModContainer; +import net.neoforged.fml.common.Mod; +import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; +import net.neoforged.fml.loading.FMLEnvironment; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.function.Supplier; + +@Mod(value = "luckperms") +public class NeoForgeLoaderPlugin implements Supplier { + private static final Logger LOGGER = LogManager.getLogger("luckperms"); + + private static final String JAR_NAME = "luckperms-neoforge.jarinjar"; + private static final String BOOTSTRAP_CLASS = "me.lucko.luckperms.neoforge.LPNeoForgeBootstrap"; + + private final ModContainer container; + + private JarInJarClassLoader loader; + private LoaderBootstrap plugin; + + public NeoForgeLoaderPlugin(final ModContainer modContainer, final IEventBus modBus) { + this.container = modContainer; + + if (FMLEnvironment.dist.isClient()) { + LOGGER.info("Skipping LuckPerms init (not supported on the client!)"); + return; + } + + this.loader = new JarInJarClassLoader(getClass().getClassLoader(), JAR_NAME); + modBus.addListener(this::onCommonSetup); + } + + @Override + public ModContainer get() { + return this.container; + } + + public void onCommonSetup(FMLCommonSetupEvent event) { + this.plugin = this.loader.instantiatePlugin(BOOTSTRAP_CLASS, Supplier.class, this); + this.plugin.onLoad(); + } + +} diff --git a/neoforge/loader/src/main/resources/META-INF/neoforge.mods.toml b/neoforge/loader/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 000000000..4314d2cc7 --- /dev/null +++ b/neoforge/loader/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,16 @@ +modLoader="javafml" +loaderVersion="[1,)" +license="MIT" +issueTrackerURL="https://github.com/LuckPerms/LuckPerms/issues" +showAsResourcePack=false + +[[mods]] + modId="luckperms" + version="${version}" + displayName="LuckPerms" + displayURL="https://luckperms.net/" + logoFile="luckperms.png" + credits="Luck" + authors="Luck" + description="A permissions plugin for Minecraft servers." + displayTest="IGNORE_ALL_VERSION" \ No newline at end of file diff --git a/neoforge/loader/src/main/resources/luckperms.png b/neoforge/loader/src/main/resources/luckperms.png new file mode 100644 index 0000000000000000000000000000000000000000..2e0ea669a550f54667a8a3c2e7eb23e11e4d45bc GIT binary patch literal 36936 zcmX_o1yodR)b#}Ix*Mds8>AZ?QW}Pm8tG02=?(#DQ5vM1fftbO5DAfz{x09Q z{-3pET)^D>+$YZ2XYYL`Mq5(}2a_BV000~nW%<_tfDHZ@8NfgTU(Wshy8&OGyDJ-e z0RRf(=^xUk*qA2phh#7XBbct69n9C-(-!dc_2qJOb@GB*yW4WPdD>?mi<1MuOF%_l zM$a$jaK+cp%rIB#;i_fkw!(Lz!Dh7CS%to(F9yo4>BjJ^-NssOlQo|9%VbOH~Y_CPl;Tm*zxM_A$f+=boztWaqi@A|GyAt z$K5Z8F^T*DHWXUyJ zS0#e<$>PeF7tpP}ZF>oS27eCd05gEy#64NyO2`%v8edNf^in6;JIvRZpKgb|lT~iz zK_Foa+ezlvO6$*hD(JB3>3`89D$Vh-z%9U*riF)kJvt(I;Dufxm%IZv`uay(Ilk<% z(@s2)8xvDgrDygG3{E;o(#qsV%PlkRn|(2=3Oa8g(Amn$Ii7(^r2l{lG%G?%ohBY= zb(~i)rZnkZyPD1i-JG+U%gak@pmHA`zE4aHgqK85?y;lRrF&VRTXDigfrL-LQ<>vP zQKwMXG2q2OqWeTrjmg)8e_<~qKjBd%;2jks!;=->y1KsV-#XQgEfT1zTpWWqp{@5W z{}WT`g22;q&I|^O*!S9J9R#SIU0ibUsp9cC3&D3`{cQ0n-}raB<%EOQYd3Mt7+d7L znN!=dye&$XB)t3XIryZPOaw8cqGDo6p^=8?Z=d3Ao|wIj}rQD((nHwKcJ31XC9P>I5nVJ~H;V=#dt zF-}g-xtN%kFO`+6V~{pt>C%05`LtFf@yf~r8ZR#|n(|eoA|X8h&-`bTqpk%5K}!Vh zB@41=B1a<9HcF^K%4;>XIFG=tx8`% zNNDda2=J8Oqh$z_1%f9ax0B7y&A13_WuGE&dG*ZAa~Ev*R*=_&KAF~PDK|Tz{z1te zzwFMF2>4Odn|vsP3Jhc9Pa2MhJOaDq56B3AjFc%n=plM+(H-*M{6il9_cQR_c8&zlSEN3-aUAylCaIQYuBxu5NxmP3q0BMU6F}vAyHA$ z;HZ`vtmi6ZY=H8}f3vfh<9269m6gF+yv-)yqrtWX;BEmOtGBqFq!(1&kY7TU*T<7w&i`uA`9ro*p~ynaJLnf&N8y z6fPAxDOIEcAu(Wsc;&MAfiyu;3VXd6XfHa$OYJX%K-Ur!i?0y){WkUB;$zJ%FX!Yd zGe|yLcb%e=qUFon%z5;sOZm6g8}$NR9=xA4D$r8V+A3O(aJYL3J)9|s%*V^r;2r3! zVi_?sH8o`h|Hu$w8{iNig?DW+AM>lJzYQK?@QEUQG2HVXX4sJK}T|R zZtl_^VH_hG*9KhiawG6kUxf%juzkNZH|G&hg-0u)N~b$HIIuwDgUGtY zkXVY&k|X$+^o*80j8>i-nRytQdxemX2Oh6cu>rXK-PR4`M|4h26+EAIA69Jgf8!^P zLjWKKA}7vwMG_rBT=5(YPAe*L<$KB^$6(XvJOm%wW)D&9J#R*Q!hpLObEMa{|M`KH z2ClzH1u*m)+>T}Pbf-vwCGt8!Zq%4HcHQ2n24T~ ziWDdqAhVNG9E<$Wf^0nvA1Zl1Q>+*sIp+0X$+#e!yUO;qy(l7ouKIGMU&!iRn$&tpfEm9taF z={PS(?UCd!+IvT*Jn)rw;Mmp4DL@g1mujpB*re?4?jC@H)(6DEE261yxDyW~>*C8; zctb>p8;3qeck4=GDdx8eeIgU0{pr->S4&K9ZV-QE!n+Dppi|^Fh`iI$pG|`K_~hYJ zh1;M7H8ga$96tmI@8v_|jTQC~^l|*}?+$o8Gon)3#6y=yc-o2aS60XERp|zJR-rw9 zNjT6IlK9-O*4rO&Qdkc-mD70OA%EX>Ja#(G*L>gSVd$s;rdkFD1{8ru)B)VZ;-@yc^T>N(ssBek0-vAxom=u4)e%s7YYLD|MoQf#7v1tX=voA0BM-Mr|? zgOrft2E>%i-_4w}$L*Yuvc^U)6Bs1*-T89_Ew~jEydLC$DtUVZ-2l9uxRJ#zOWuEr z2=na^o=^IS`XM@oBFsq5#i_Mp`4btTfeE9}QC7b+`3H{Qf=eI^4$A}_mNFDtOxoQA z{1clx@zLR^d8RtARak9}Njy#~{P#@}eF{-luE3i=btn?I=KGG`^aMqk^^AT~&ab*c z);fcVi^7usD^QeWKKT9H&46ep02l#>gIorH6Z1iEOz@`Pdqx`@7WWC~`v}jwf!GIJ z=65f?;|p7(m*eI|VDbz|&VuJl7a^nk7o6ztUSiilG~F6SjYFEe?jHm-3lFBB>$zg| z9F(a{Jj2j}AOgB0X4)hm`>?zvq!hEh6ELCNf`I@`D70!yComlF{Q2=i52Jv;^7bjj zy27z`N&3cPl(2gPyoKIo(F=zqE1OU-=?q z+U#6EJ~kGZz+JfK`@|!*PqQa5pYTr22)fhyA2I$%&r+l&6Fv?X@rBPY6Lu0y11|9 zOhCnDwS7TCOzf5>zFth;9zTXvT0s}1s+LR+e2JesAdESECZ|<}#Y{b^a3Vu`#0}ha z8F5@%XT4+r0}O+nrKKep+vR|{*a^DWp1%6chL_~4e8_Is)%(KX9413mATw*k?tVJ9qF#IpDQ^;c?1hbo7G!DbSm-4G&Lh*5#&QC9gYidO2 zf(pH+cuE+3(zdnT7=vgc3+=$qfThL7U`|mCe^g>1^$GG7R#p9G&pFeg#R}m*-o!X? zC6_zWhw_+ItbN$mq8o7E_2*s;NlUV-)rV2y3-8^Dfq5qX=T|4vFEbyd=L7}#K(slb z#X#_gwf+`i%=g7_92>kw&tEmB9wPEJ=9@wNf~x|osK?}__nPJV!gS(PNrP)X|Nd;i z_g^j~VgfzjNy;WtxW<9+6gAqsR`9~maKkFFvNeJc$%d1?LnMZEvB+AQ&JiOARaSO> zZEQS4MaVqiL*(ngPG8T7t{2@*qH1MCwV5(!Vs|W`vq`8T@Eoyo>$bWdNo-A%q@Q(1 zEiEmDu!t%*7a>c-v$DwJW`5n_!n+7_8Fga~CJU}>QCPl|&q{}nF}sf?&gGLr*OTR7 zAL2K1q^!Ozoh&xmzwk!4gAfB`yJy#`B70pZbvm$gbrv&X#jOc|O(3jCs%Q}sTyFiD!941@s2GLM=p2`ONWh`le|BvMKj{k1ugo;gWL zWPUQyI;HS^HR&x4%fNrp$5;3myaQnBe#+f44M^ri`~`)D8xryLw@CY!Gho)OX!9US z5R0;Em=+C|ea%U&eG7y&pfh>@}7~KgUaTohfFb{AY~cnm2(oe>z8&bLQp?gDvAo4@#99=>a9fEtwBe z$bWDV#?y>lqVb%*xCIX5@%t>{Hn|&I6qk2~tdW{O6_ixU(K{?o#C(qy70^RVkca_C zZ9~HhC%#N-2+-pM@Y=+3!4M+3f)lcb_EmfDp6lJ!o{k5Cr>MQ*$$T z5T2lV$iK zTtL9B3PR>wKI4Q)hn$@J74N_UMu9XeU2n81tAB^)P%c>TXjUe=dZ6`--(}m*yA`RI z1uP>8=r5?9iK*ndD*)vbaEeHPF7$O0O2$%bJ7Ntt3fcQF^f03rwB+6T>wSbRej9^~ z@60%PKoF~|q%^2o6t)jOC#$xlWe6Cu4dZ#ub5D(53nh$7{e0o8A`kBOA-V`;yTgZI zvYSP3-_CCAy}NNLDypOy+#+v1fZ;Q^qHYkV6q*%LE~7rj#Iskc8%jsB(56iAmcG0G zFML9}h#F~E%!+`QBO+ZL#l>6SiozVo%vaN;a$u?j^y-fYN*C=H{#IjMq75U6WRaMnFxa;vbIPOqP zIOhQ45f*kS12HojOmy`EUI^9j5+oMHn+8_$(Z6%;nU4jwfPMkbnOKMn^Jt+5eum zxuc|nVe{%XpD>yij8XOiiI4sy1%+xfelHlz1x1 zA1d)G9GNMcnM9I4hZRoG9lLCczpWRUVkr@q>OE7}>KOW&TK$I8Z`0``QoP$E7;z#@R z__?-rtp^eyUsZm?8v*SVLw5M?0;VIn=QVuizM@lKN8^rzTxt2+vEKfu=y*#h$hoEW z;qA}ULC@2>7ixtAf=kY0JOec#%zDE8#txT4nQw7$aIzs#E##@~h0M!kNKJb=xoFIx%k!5U2e0@e3 zTV&?{XT8S&oCmO8goBxdO+n`pnT06Vmi2E{@AqArHtWflm_*rUaS=HC|6C+-A8Bb$ zjTK-yXP(>r?hEE970be`qL{{^zjO$xZ})Z}5?_WBUjq;wBHPiPF!VNgbbQX=Sn#L1 z7i22L;U);<^rB^<2&yaCjK#yVAarxTo28w&tUl(~LFrNz7bsX`BgvHbM;O`^^0|JCCsNX}1#HL#S3WS5-7D$Y!|w&kCA ze~BLVXZdObFYUbMF%S&APwA}`hxTW?-ZO`oH`_)Y3(x3u(XjKI;u0Gj;_Cwybee^8 zC-RdL5^RsMxwEkV0~3=QDDW*7x4|eLN#olK>$3o~o+hsjX2))kx~nd=sZ;IuOhPU( z6OgH|?C96*b{kxUj8!H-AleAZ(Eie*u9edMt2Mr~VLd{OXVe5#@*k=>0dSjj{eavi z?^DLv@_cq^X(M%VgAh|iC|ga2eB9ipzb=9@C?xRbtRBhqjF7P*3sktzVtZzc7wy4K zy_B%{2j+6c6#X*xIp}O`k)GnP#lX*bg*_vVIQGQflD;Iwu_>KDma@bMVjB{FPj}cvKM_@9DV{D>>8loxJD> zrU&Hz^Mb%B)Kpb3|J^@;A=DAteB`!9$O~0^{0TJ-T;<*doLEvAD8aWhy-8h<^Ei}St z8MuGnxzK7#?hsSae<9F8-3lTBBVnkclO~zSpw)IoD-4<5+(-RJQ2&`9VN9Pv#^^<( zvk~gFyu zBzynmuae)$>&r+|U7?YQmtPhn6Vuyn3n3M!%(cR2?dZ8(?08i4jlxfr20K@Fc*JhE z0|~UdL4#I3iH!H1iI@Vgcb7FiJ*{*f{0QIY_*JxoY&BD+;a^!Pi7UX6<|{G7!E*poF?=SBh4R@o1eMK`{zr` zU(5s&FGkIdsggA013#BHY)>mfyFJFM`z|Xg7u#Kynjb~pomU*U+IkxUpT>7@fn{3J zLD|_Ap4jmdG9s!lW#t5Z@zYx3=<)r(#jRG*6$7xw)N{tr!7uQz#nFu(XM>0qCeXoo zo8LG)kL_RGK5Bu`Ze~Tlw7&k-09XrEOcb^F3Z=|gnbjf{^t52k8|wdT?>HhT8>%O+`nf5-uxr#~1r^lZ1>-V1C7R256G>Ot zEJA0SBd)-2TwsK}EVfqZa=&J6{HZ z1@r;~Sb79F=LBFzjI&v_N!X>E_+9gP@eXXnqOa@$;0_iyFSWwRv`TyCfyphX_&fxc*l(BA)W3VyFjY zfP;g>KUSzZ;nqXxh3@yu#uc7V4rw%eU2B0b;(>q$`<2Jn5OqrMcNGq}w7&k3yi^wT zSHy~OvHz+=_v4W^3Xlrns35;I5WoSEaFVN=L>A0z8Oy&j4a6BE0LbLoj;IsjM}fAgH6ANm>Wpz zBn7%g>_Zs(KOyT0L`?mgqa~U<@U&e>?W4zh_}jnphh}!5PkQ3|2n60;Pf#!`zxg#- zrB6Ttp25!>RWJ=AJyxW03p?(I6^chRTu&^x2Hk03|@Yk?WX8}_{Ku}Q73#1u_L(syE z`!BBY6psJ?$7xJ=(=YO8%y*>Gb7{>vJ(_i2WPM=D13h+r!scQDox~oQ@pqZW0U7sy z;pem3YC1ZI;`h=8mayc~scrqT&dyb&bsa#VsL>2V7tSuZ8unMwyc$AF#sgbUlzU;n zP$;!+3m!ji$xIYq9U_q@E-2h%dtie#Zu5}(#YxROX?ZWbj*pNbQS);90_HCv{(%PtSYn)U&^Y8(JN-*hGz4A^pZM*H zXP)As0DPth#@Cmga;?pQJ?MQmK$5*TftBaBJpbWx?{ML-%9uCtL8S2i42SLxj1T|Wynwizs*(IUJj+_Z{b(~)vKB8D_zo3$ zu(b0;g`&*e?DW)PTVF*gMN}$9`A!=a-9M%N0xUS%sJf+MCp>6DB~|{w?{9jRF9o;EmpnCm}WrX{E5m$@*oh`MHqL=kBzT zo$p;@FG2jk)@duKmZ(WRSHC$*2fA(e?!_2f?~vA0#n3;W4ld*8_nP}WOBCT*w(V8U zz)5@Pr?Ge9`8@PF{-y5%2MCW@%%yUtw=8XEW-4O{t7EKAqeBO~TCwNX0?-tk8cyEn z8i|FCpZtYRCaKCRwIqudBmAV`rV8jSkk6wmPqFQLDDNc$!-PqrM09#vfWjc&+v=s* zt)!iTThte2N;8ca-z1nLcQVx9T&F+(@Zb1;Wz_mcNX5WZ%pv6#W1PJQllzvTCasX# z73LF)7QWU?+=r0%L6#Q43fc)F!X>|`Nc;qtj$-7C2&F+b<80VmGEOMyh$ue&(5!66 zcPe6HZO=aNw~3(+ky9;=!qMy&&(c?kJ-Sy_>*Xg#ST(Bm9t^1-+x>saLPS1(G>vC2 z!lZtH2`Xx-1z8!qOi`{Df$b+ZW%npcWEXw^{?7?;1p}odcwB`7H`ERCb_Kz?t8vpS zx}T50KqXo0&#&HZLb^ZVq6`oqRpeKaN$|PSzM}C^4iS>uCd8uP@%tI`k&{Y_GV1?% z0c2Pd@6RJS!{4m4Txk^kcH}0|aU^XNA%jw93Sp!+0}js4%NXlX$*|9Ky6`H=*|5-$ z)(9OwHUd7YtEW#$J2_6ZF2fkq%{?umHP&iu5f+gozpWjy9-}C$?%Lf8bK~7E$tTuIa(h0 zYlQJ`<3=rtk@v?Y2{A_xyCn3aV1E(h!5yb+yMYAZ7IyUGO^7GkS3%>tEujdtEEW|E z07PRwd1=zY8a^xW0K8lPvj+Kre%h6E3~=URXTPll(a*1em{Ja)-Qq+F@Qd1~JTq?Q z4Ocrkn0L-Nd(%&k>9b}TdH;e)twZArxxH}XVA#*<(1sh<@w;HrVAJS@=NcSCw5jC6 z+tRb?+6KoYMj$D)!6n9oUb@0sr>#HxOZw%@mHM7(VgS^H{{L7Y3*9ge;eu9k z7Fw0;T5@u|f>qigkl<^|i*S45j7vIr+;N*8 zwCeXE^2{Jns2z08z@d4h5vh^hCLF6g^zv-@y;EFl>?IoVdyr6Tm(QvS02Sv<-b)vR z$Znb`Sx=-d_S5BEy4Zd+u5pG{zHi%$6X{qA>v-fmt+7s>uwYvJ5Y6iM5bU_8S-w){ zd!5pzzN*q4U+*R$Bvfu+-o%((2JmF6=L6d8qJ!5Ebv6^pRh%%D$E}AiQrz;tCog6b z{I*j9DQ|+_??b|E5DkrQv%jV0OS`(AkeO7 zFLz8TKsvO@6RHf0Z(4ek=3NZ=QakX-h(e|5H(KKsMraNbK|;I$?{_mzhA_(gFMw8VT+4t3v8 zxyyGrXYz=KYbp3veFKDFl<<(Kz;5oyG~)6U9xI@>}(<62LrTw zhvp#7Kl@T_zSgqK0*0}H+TBu$|HCV1a+yMT4c!GBCVC1Ygzm;|CYaq=IP*!>hqs}{ z!(2yxjEK5MZW}iwknhl<1Zxj&(Sbm0#p2WKA~_5fKMHbkS7i5(L)d4ZS4sN#a+7YO zBFFsx{w97c7@4^)WtZ{RR{9m!0^P6qwT%z&_9oPykS#k`+S`-d437z40RaIscD}GW zUnS5dk;3bfU$NJP2Nv9Ft70soiYAOjn0f7PWOTHwLT2i1#UUfTagWS{&jS<99NSo9 zBEesy?I*K3UoRmUnR${d#MjrJEVtE6S@*ZWm{A+1mC3SL-Og`2T z(U+GjyYoW9lg`%971g|4v5z5Njy$-XbKs~RBp?Ra}Q?Z;eF~E7=Ei z=6D#vnR@tKR;D6gl9`lW$=SaFBVIX%nuhnn#8|x-jkqw{B(VbScK0^C;pyJCHZ=~ zMfUO~=3m+A4PXInD-W!|v`dhIHsL^P_IbyK)>SZRYh$3}RAAn%1zG6*n_ZOear8}O zcq-Y+`IzvG>W~=>Fh3VTbF|*izP)!S%NHm{l?MGdl6&G?QrrDJ5tH8c>^B`4%x}~y zOWhh`#@T*eBw$vYjwLuAbISYy2J?n%b@=ta)mP5~5(C@X`38y_Y>iC07kjknJ7S@nM235~II)`cmqnRvIqv}FcML_M7IRJVcGu zHqM{K>A%RDo11gNU)XN8kkEsQ%faYH$F39s-}(@}e-YGSe@{y-e4JFQS60Vb= zI4#XPBDBgG_EBs=Tbtf?Be&iH9&hFeDprHE3oBC4?sPG9SO`MmuQIha5|_;Uj73iH zL|Xk`<9C8TAASW;W8#OY2ybYhsUvZe z4OOSfP7$1x*M!)E)Z@fda*a=U@E2l?vyg4>`Zgunx@?ANPaiZF=zCI=oFhQvi+yW8 z!x5^q0Qo%tc(hgLMDC4blwE~>h~60b=+=;} z{;DegPcaUr@H%n9Iamw4$QM*;4UL2due_9+_-e0j?BNN zXm*CopE}jt*@6w=>PsQ*y%8GzMiB4yP@kTj-sHNJHHX~2;oeRG*K3;;KHIsoEnobv zjw~!{dNu3=@uh@zc+A6je$GBu;tx$s8AyyksI3dE&zx0}(&E`^E^>d0lQmg{sWmXa%;o-LEXp zku&GzHEjUEyuCZfx<_CV53jckCyNdm3icsn9zNVh*ZHbo)^P}7xzA?(D5x4>!-{wr zplLR5np6H7tkXR+)V>keL&3~D2b1ty!~`a;2WKvRs?EaMEC(2ASV^CgZAiT(rIPbq z4AzfE%>1IlWO2i`?GN~1qu8U$I^Uue!&`9ebNvo!dLb<@9l7B1=g(x_=`iuj*E@z$ zAQ?bFMNf~0E7q<5R^hb<$i`fZV=}z?l`RTA8%Qx_TrtN$RNR!*TH&@BUs>kpd%@J0~e9sZWWY< zAc7M-o^1DOa}8vM@9jHvBmJ^)jo3^iP|_08T~kWgjfE1X-=OB#cHd;qr8}vnwIP zt&U{#US;MC@~F~fxqjlLK`!70Se!~MfCl%`SCMrGBOVA|SgFHBJggkXH}Lw^Bo`y9 zG>F*JcrP#Y#0@H|yBNiUx}%jGk*&%+aK*1W_~d&(2OT+zyfkL1Vo&@+vkz|43>z`= zMBd99Q0w?mEvGq5JPOu*f`H?z{Ht^YZN!IMU#)rpP7Qj}SAxz1o%tm`ECn_z0@zIV zG-e8^zQ4>-V25s597_VvwKDb6*d$_$4%PcnN=v@3=X41XC#A9db|pg^o%YdAupjn` zA`m?_qDB+d6(Ryzk!f!!g6sdBX$E6QndAqjILmyP`G53eG3QCZ4`k*Pr!ezI~}e{KSVV0wHP6(R#cldeto!m6opu$UXyj$NQF^ zyq;`==zKdI*%9OHv$^OgeREe=y{25VKbvY){JgG@bH#j_zCKrZjoSV5=g$j3HPt^} z&&0o9xZ1cAZEmwjVq0O0t@rpO<9+$MNe`rCDLy><+hwGVp4;eZp(eg_p0Gi)@Pc0!7 zWH^V4Ksi3Y@*9>?b#eKE)vYls?$4$#5Mg6lNekkhA?PED0%(Y1ycga{(6yQ#YoMj` zAEo<#K+{?Xacro?$eWIKKJ#?_QdMR8cep zAqG)s_xpUtknnFHnW)L=f|YZ`Q6pS)?JUHh$@W*lBqB+MK~0#=r=C8K+46UwQAk$1X#^Ciyh;~gziKmX z64clL(A9#4bZB&tR%UXfzh#AXK!5_Ec;l!>RDl>@^CA3j^Xqi3h9+M)YxiZ+`G}un zkii?Y#E+bRb6UDZx?DkWUM|3W%F31u!-wUR z9?+KOKrNl#x-AiARR7WECQSvT1Nb!11p439GrHikm(aZTdN5SG?u4;8eF}|h?YAIF z8ak_C@FL)iTn!th`e=RDx4~7aQnOID(QN(SiU6G`nm?7Vhh8&v<`@w`A>_(|R%gqq zm5%#$URjV+urCgOx_ky3??$(0SmbIy+TB6mq` z5vbA}ByBFNYZ;%0Iv#Mw$~AKzhHzm~>TRYP9h>t<1YQlJKV=1JX{Hc;{novVx*Uajr@ z>zRqL0Ll9?soL~Q49zoqUD#dzlu#ow5Jh>g93~%JIG5T{PDaSviS-#V0w?i|0L=k*2v73x3boeUJ3utDHdSApU%1aw#nCe8#25gL4Byz^PZo9A1z53FIUCYushaHlvMf!s9L(O?ENqCZu#40 z@pFn-@Rn?s4=-vrj@nSFr}~CbbLQLwN_TzN_Qr6nA-pP8U5=5obb#z8b(yHbGfbS6 zqCo)M5Pe_sCDanSG^>-HAXYtR0EyoJB`>4qZDCfuIUbyB=C-!SUKx6yphn8J6YkOh z%dmI23_@mU-`$6|i8CJCc(bg3pxb1m+-EzBv(lSrih`FTbxHH`mzde^p)1n~~ zCLWpMF1mCnM%vjOQ?&4^w4qeK*!NI>5}!w{q)+uH%>DoRZ{H$rx+0=AnS~%#I%uG+e!9felV9aOsw@uKk z`%X{H&`aQ4JOdDGL1BvLY z)+A_q9yusS%yad>X*mMv`}CTc8Z7FM0SIdN>FKEpXqmEXKA1k-zoZ25@KuOqf>dxB zImnDxoX!`5P+OZ&?XiOowPI1W189I^4(`zGmk&s(DL2WEZ@@&k-@QFizybz75v@F` zBOd>j#~@)nz)mpleyy@$cR7$!MV08-jKJSBL?KmfxBN^#kX@_ z-0NY`s&d1fQ)rRJ0piSLQ^s*c^I>AZ+uOSZm@DVdBp3<0xqI1fM=bvERki)kqpAb@ z8i$u%62FAAR z8Y}Vmjo33r0h6w$ce5I8ADNjTjUjD*y3K`o*GbkIv-@MUV| z{H&HkMs*|mT+ zAW+UcvH3ypp!oLjn&tzYc>VdXh5XvYu{S-X*-hsIqc?xu_wyE=boKU~M!%V^9L3>cq9m9!u5#FOa5 z;!BPK;H?kC{ZtQfl61Oi`Mzy;1X<|GT>7R=?kgSk)*jP*QVl$cRk6!Eo?|H5`WNkT zH+!6b@E&-E`kl%Yl$1>-gN2X>9pVEk^&YFa;2w@_P>-+M%AC6tU|FT5D0C&+g?TK;5U;#qKL&u19iP3X~Q=r7Tk0a5GX z{Nv7bBoIi3;iW^Muc=0m#InDt#{W3H35EmE{rSD*RohyAaw3ro~!Lo0te|Ft+ z{=54cHKdSU=p5GbC$jk-2Nhn#0#2sF-}qb(70~2ZWX-Dinu#2CSZ@}7rzXZ-)RY&A zL=wyTM8%yppL9f*Ek#-JsMI^h?MHK*fh$4E4Sv(ejPbH=(9=-!ekZhYpH`~7g^m8; z>(~Ln(A#)EQpkewBkkv9q2%}rAuj#Ebl2C@U);Y6pk7m8!soFCj-Pr4|85U^XO4OW zgR)HCYmqrEt_YC9KZM1Ca^~^5D>u0-g~hqa<_{B-=kBzimj3h4C4lMFSgf5z*<=NF zQ`Ea+g?1RkUl{RF2+;b}oZ4eX^{2oWGU4NCa8714C0Fdkc43gW@*AQ1ldc4-XO6=V zA;vc{doC~gV{)!)a9QJVv}`syWehg8Z^653AA{y^Bd^gm*4+yf5#DNuJ(w2 z`sAaaIudAS@EfHcGN)ENT_gj$Ln4P&6?*!4P=~58q0)C6g^it^!N&IX zdzl#zn-?eXt=Q*Z9AFM)1o{X{t30VyUhuDvYZC%4@P)JrI3<$ z)q(b!gp3-%3Nz}PS(^k<2se%#rj}RwK2mqWT>Aa^%%1C^f6w8Xzex|3z1QKLsdSdt z&$N_i-hWA=Q(oN>c_=lE+|P+^J*PrRA1rxs9~uA<2=(6xCHy#;Slz1hVxzahL^q5e zbp^8pCVX+s&c8V4Z$;VsrshpQY!E-_x0BcA&*B!aE#qfs=c>8Dm56FzxTX8_gsEg) z!pS*wLD5C%j(>~k{A=sf%z^sOCBhf|7Q`aAlEQz@H+^ zQEbYZO^ncvAKb!@(6c@S>=q9+A)=M_4BMNbDrzoGf$cVVD6a8&&F`H~291(InGTfg zHu|()Ln%MfW5taw(t7i42akY{3yn&nWtGoqk)Ig1hkT_h&oha{+)IbK`Fm4ZgT4?8(4# zO0cm-W(B7hbZ~TR&@ImI_N_j0$uN|3UA+M<(4e$Z{yA5+S7ysBtZOo%6BnI8=g9Yn zIm~N)DF5$N$k2Ny$r%*i`ewHq5;m>87`PzdgH+1~fE~|v1l3E)4$a%C^7anMFIBpF zdU3I-ec-H=Eu(aG-#0-wv{gRp#`Al@yY6ND4%1`G@Ha#u#h$#N9GMf;69F2;u{#{l zC-W#)P_ZsePH(mJw?+b5by_XDviUpY(@Vip@gU>j%IO`@*XJJr`itqcmI6GL%0B}y zGkf(T5&S;}iD$rATjqOH|Bwz!#Jm0ap!JMu<#zA*JnUY8)Pa_c7Ukh@1Y6VcN*poR z9yzGO4tC}-nbL7m2yzZoE!`Ndv0-on;cmQxSCh9f+vA5V&2v`A?eK!6PZOXTM zpZFCXj#Xrvx_4u?4kls6?jOas?i8OW~Pw(_L*m`vz4Ul$D^vAjP z+VSiQiBP+pqJTh2OUb=uF0M&R`@%~sAi@G_ksBvnhYT~3n zSXC(O`7+snu_y9I4`TvFnnnUxXkTHZ9j{Z$%=6C&cQUZ#mK4K7;}B0d2(sDQYOwP);RdoGgklRcfW7Pe+0(wz+n8b zyF~-Myfq=(*Y>nXbLFbAxEQGOJ46r7j>V|}IsHSEM|rpKHh z-~pWx#M;yPwUuCF=)lXsQTqvh)ITF^aL@O~0C1V}i3>MoXh8QThH>Rb!}zU+HZ-fI z61QV@I-eRu$Z8n4k>$%O5c8p6-g4>U=u7XSz@uWc1)Dw$w6He{{?L|ziLw+q444{x zCM}(}l82E(rV9t9UaKRVVn_c#a6C)9eVHp>36e)spEU-lNBdi|l{TgkK0P&br>MYQ z`!l~%fQ8@_DqNBYKnC6LXmRW^=5gU;VXTx(lFLaSIY}j_9O}WALcgEyYwRWVSAQ3Z z1yN~(up-zZ8uU=Ji>i)%$1H}3()!FwK&vs5D*$)mgYqBj77}5{_gn~GjFf3m7%#0p z-J^s^Iu3re7k~1N@^AfUGf;`m4W0-~@cp+jHm}58ua@``hybjd!Osw2B#5CzI3D`n zWbW&jNGYHKw0j#|i3O6V0XJGW*W+=mcz zJKvg`APJ{_?a3;2Xxdu*5^8f%z=!BWnP?bjiZl94{s>0b#sG290fP%t2m9e29G-Wl zW&$hJQ~mNDDvP4FHZ}~C(O%|AMGN9L3m2@u#?SJmF__TyC&H5r8}~RKyd**M6*OeH z_t&0y^my!A{RbNh1{>)$8B$PkAfjS)B*`E`1;*^WQt+kU@4p1BJVs87N>o7?Vbg|!jQV|96>7N~GT7^ZZH1OO%>wycYQYDQYHU4ZN&%l|g}$}*)-=9!9|gSowhstHBwi`pfX6M2;ZCju`ZtkX=>JY{t46|g$M zZblYM;*8T^dCwiN_G|K(!aX!vBGe5~IXGRyqvz>RdB=9}$f)Q5LTXZFkxfiwN)EUT z=bndW3hZ7-lqts&F<27Sj^{t|ZS<~xT;%mTO%C#ps)8Q_!UP?HI1EtK|NIkIPos=N zB~=C#nB|6dgT*B-f6hPR_oN();1@@uLeQ+6tXNtYMIcs=xvAx^R@NzMS?`s%8k+bb zC2Be5*w)7Gz2m3jBB9HUap1Sv=NR5?32Sd!o$Wt2RrKUH=h$;ZY}@;fH>mvZFKEyr zQ%Ox0^mZv{Dj;^#`n-hiNl}7PHX*ec;XRp{b@TvvV9CrS4uJ$RwzZA+oiN0e0h9r? z3aSv}{L^RlP?L?i-6QYVMnzFs3S0>}`e9`&-&SPlXbSOoDu~Phj)$U3_=~Nm$FoBd zW;kRjvkDXJ1C@gKY$V7}X*C0NIZQI95i$s+c^RDE`jMpmKMe#di9Qor>iK1DX-+xL zeS38X`6x0rk7R%`oi}=~)$`+$q$)C1V3R6!!2_IF{OeIhSy-&OF!Zv*YprhQn@X;Q z_gf$fLmo$vMoQ?x2ZtyCu8mS4i1uYL*0Y3gkb8nW;WnG7^2(huFgA!|*J!RzQ5af7 zg~&AA`i;9_N|TNac{J2#w0ETAO3@W^|*;A$bF|RiK;p9IR~^b6Vg?ktjm} z>&RSW>1O{ZAGcAUl|&BN>>HSH6oXHiqV|u+^KM%Usa~o;tnaffKqXD91hypLvQT|j z1CW+^AI_Nc&SEU%8tvY>k;WtBN(a%1z6oNdVp{2&_JL8N()b}Dlp&0D-i++1)l&tA z7XU8dp7h>m1Yr03QrI=SZgjr|#5IeoHH!UjCI3mBK31 zLCfHVpGGZ^SLm)jlom)Dmh9-!}*bvOAnS|22|&{R&QLIXdM270k7IqR$^hK_?R`wEvO;-L za|IpxK*K-djc^#RQx{=+_Mvq!XFVIP2<2ldZY7>M`YoTbeth~;ASt3B>C521K!OIE z=U61m0)cPaB0-%6D83RaDf+Ha80vf(pm2MW+s72srcM&nXhx7($w=xopMfakG7t{J zGCo-V)-bb9Sd#hlqCH1q8CE+n-$q>c5VB(mXQQT> z=j`$?)28u58hM@{6_ea#oC00vtp(!nBZFDk|0MfL5z%otHWJ}pV#YLE zde_cwJP5G{^ty0^wTec7cnOKQ_4viHzmOT#=KO6Bx;EkcekgHoZ3qJZpX=u#LPiiL z-&yXHulE%h`HbdY%zN)cyr_YqWYvmI2ZkEWeZC4@24@LUiRWL$C{a;aV}r~kw`#HA z6y&VFU<6j=GpJ@MjJ(7i!_vc4zr4ZNC(b-P4i6>JiUj4H{Kgjpt;!vBPs( z9T;*74B3&laMx8@tM(_cYNBRT_Rrm@LF;T3+@4!j%d;G=}dhX?2N>cx4vTKopTwH(elEA!uDpjG}) z8?=bmKoBhHnqazC=9O_K&V=K)!dL=fJ*+a18Cc3bNMfL6EDed^`16(i-*Rn-(I2!wrXDM(OGtG-rxrh90gNMzRQ@fW@m>f< z$!8-YL+*}1eTs5D%y*(j9xa?4Gj344?S#x(ZJvDRJnnlQia}#c`~KuIo!C?|Cl~Tt zDe{jL8dt^QqQiSTrsF6(ct~1JHdNq+A4uCCKkTFT_+6Rfgtrfcn@N6n;hew^kBop` zb_j~BUzXhH<>#1+dv=5^D6m^KcF-T6qf1Ej-Q*zZbKS9|fm}J|*SGE1CwET}R+P+V zi-i7-2$}9$-~3!Z+SD9l#0RC<=b3$eN1o4snGts03?-GkW#x~$YthDvQ)VWDTwLWG zb;o9l#>J(rt2<3df>0msbUy+sV3iqAuO$uDZ>P#|iHWQ##G^>=-vwaxn+wuAPbbgE zwK6gaB<96!SnxKuY!1NAqR9lQL3<(qdM+))%_;VEGW0e5Gq!Ep8sjW}vWH=HkL$|) zMr?@!4Ff*^LD5AN+k$KSZ8MU+WNO&8f{f^MQ5Atm(4F_0?L!+WQ&Qdlnd$ z&$i3Y@B9DZjYu#L?w(tB>N|hD zcE$X?UhTPa2Je?wymI3%GDYBo;w>0W&U`{lc@M*SF_Nf?k|Yz#mjTb`g)c)Ca>dbE1SwuKl#3kR{r z+KNf=CmnrvI?f1sf%l9XfR%hI`CC_{`{}I zX|u-8*XIS7H(G5Ea-5n0F9H{_7jVk>mAa=rs;R2{WWKym9Y1Z`>lF(FqMG5!Czp>Z z9rTcGJjGz^wO}DLEghpQCCbWCx$&su0Qh3@$lknZ+*kjq?Q+5~^co)o|GMW={`68b zZ7s`5*$-p)T4pjIC!ok4JQW!qBHh+M;e{;?cyW}P;l%Q*YOAP&6Dz<_Cy5$nmG($z zUhu^9#;POm#D~d8OiB`jvAd_Y;j*H&k?fED<^;jzYXej%_UazyzatqZDs#YFpjyE0 zXwdz{DSUWBc)A{5>|~9lD>5nm-`X?NVYw2Kr!B7CDr(Z$9NV3AT&PF;Y{)QOFTlPU z-3TuOUPZX*oe`$={Xmj({CKE9b+b$EoCj^|lD9p-ZjRI`Z$ui5D}b zSoPX=IHVtwyWXZNxgkSKo+ZA*u$7F#IH`O3&dHnhLyFSRc;+%qhLz193Bui91CI{w_RMJYP< z?bQqp>F3uJdijDrbBqOZM}4GQF}X(0SnzCTji%NM-=a=n3VMj2BVoy(8{i*H^N&6H zoO`8A805#(@t)~!*42Ffar>7Iis_wvCU)(WAugXJ-*@kX&vzmrQnOeC7xl!N715eU zon8OXoZ)+LKkDH6-|DRE$uu0~_e#He+R#Czp8rIKXZFpvt;yi|{A-;^FQLqt1*c4( z!7@0$qViPDc7}N-nDp~ z4ub6Pth+CN=JlG(Ie8&L%SSAfVKx|`7qK=Di5~T0MyHH7@LDb{^Cn2YkS*Wc6;~pU z2%S9W$074~3OKN?{2AV|O7c=44(d~96}RZQId^$-AzjGCRQ@dDM#Cd=HT`Q-3$6Q{ z^gSND^1^i-`35`k$YYSy`>9=>A0=4Aw2%v)LM-nKJB3bl!_@bO-$?T}eVS3wMw^)% zovb+usMuRfiMPF#(rK463f2Z*y0w{QubX|G-W-r%jGbR-h~IyL-})EWTxcOTyte|Y zS^f{cPAexbSqsBH3lNZI8cx)(_Gm(*>(bz0D4wze6xFBR!f$Kx{LHFi|D^>pV{*{a zyx2!+3&&?kqiTPf)l%U0{#}yXg18_#_+v^Qu1(KY5L_S-VuWdCim?vPY4&_mK z{u!42&t?Cu)?dgAt4!^2h8N)f$?+Ha14V z@2$bEEa{}%?_<~3*XhbP%{z{{fOU$~Hl7#AgSyPg#LR#m;kq(=cH?;(F*vr&+RKR_ z?`is(lNOq;Fmzgs!ME}Gv9+e_{g-Dpv@f6-?P2))MJoHajm`RZ+q)MTY-9`HR*e4U zUDc6x-c5LT1H1yYRWt27J`c-+VL8t)tw#Gu}uZ2*j{_AS1DRR&EhyIlBn%y7a zBSN(wlqU4B5;|D=Per#rEh|3x_Ne#(1_g?rO8hP`JDp1i=oM)tT_nG~DafhtB7nj9 z%@yQqG|lP;@;w>k+%UJ@`tAj>1%Hv2%E0D-wnnRT^O!EoNuw%abQVe7muomFK+pc(5GgM6#a+C&Z~)-j5=8>- z$gJ2d8rELo#=N@?*faFHs?sv<9t%A#xf*55-OJeD9Tf*Ro_|ZFk)!iQUc?NsCka43 zSXU>4TQzHSExz>TpbK`c*7`R_p*s1^aJRtTX5ELc#RW%tC~b#bf!w0-4E9r)lrkgs z-5@O1vktzMqOP!mL1O&_*A_uH(H69M*KX^wP30CcuYC1#*h-aOk#RSiz)*wdH)o$L zBM$@?j=sKvrKF_HB8qv2v29hOa~Nj8dq0(-x%)CezOD%58yDr5WrOE` zI9}g3s+{%G%}2;YeuLZu4$@{CyBJBMWUev~ct_J9Qc<5zc--e01lhSFuXN>lcH1b( ztgZ`dglEGlhCImIs{N(KQzq90M`u3saZ??rYUv9@vZS*3@-9V8v5?!8g6rhRzV(yH zZ{TTOJ%BeGet9xvn9LH9RI#ev;DX8CBrLoAv1W0|5GkrWbK zXHv>hSGZQ@MX<>T$iYyoQEgwMfa1;T-=3knrakXw?Vb0`xsJA^>AZX>j@v<0@FHU!1tWA_H znTWKUYT0kqb2gtC{e-t}{lSxfy-EP|=e-PGF@}7!MxPBh|JcWqs-j#T>+ltHkTgs6 zoTZ~^N>uyngOInJ&+KmWkWL9&tS?cpS=k1!Js2xVpWxqaxzvw%iF{!H{GrwsPf_pL z`w48K0@Oc>4Z=EBiwwgN(c4l#gZy^*v1iFn4E6Yt^iZ||f(g>4(#JO}6Rv?2X4Hih zgdtKPv2S6mhyf)NitoN&%nRFs+&+OCw!%E-9I6aIly^uaa47rN@h9))BMBYLueoPd zbLsHDmL?(`2i;V9?%ppH3D%e0cY_GRa);(IZRaqTw~NxTuWZZxe+Ro8QK zbE`g_fx|jmVJeH&rXz?#r=Bbr@qRuN!%_XFdm0F{|B!(zs2rLk%s+8noVxe>Ap-}{U%rLCBt6LZLpO{Q z;RI?ewVYxC`N)cvSuMzJ4%=)a=v73%yQ6YPL?N?Huha0?5{?B%6fCMj>dj(joC=uS|vUGL0l@rfs$SRD5I_jLXKW`E$tR7z!4U=TdqQpztqh3k3Zg}4I5Gq(%xAidSwvG5o## z)t~@DS*bO!F>h*ko9B_!v|+C}@w^8o?%&wsLL<0_s2n9$UJ&o6Ld4Xdp3*nTi0Xcb zdp%MiaU5LsO0v1*{D#F_8Bk$Uw`uX*S2Tq}p)Xp!ac=(WDN#(89$HoELf1RVxST z1}C|+o!UdUv2gxw9FnG5lnX74jd5(5mjD}QVgAuFm6U`=7wHg6G_5a7xIAQWT`yN+ zKBM}j^0^RNwFWNv^kr6SLJ$C_*3txZ->RDap|cq$aO81-8>`gKw8h@Kr#QR0y`hI< z;A2fDhdg*_jM=2jdFO4VX^Ffq0FM>U`CnXisIPH{+P=T{qphv2nbpvH`0pKS^La*< zx*S?~5>jlV*xgJR+WptpsSZ67KYol9zjHLuic?Pr`Y^(hham9iFO;gqbt8y7^C~JY zDU6Rn?r$Q)U!Sbpk<4?G9JF1P1co>Sb2V%9<-EbVX)h-HMWdYZd!0t;UT{zYd4Roix%4+_0FrpvJAc&tWe6H=o9-Xvq$qa7z%@7U39CztA0TYF}wN=awcL?DoETT6nI| z!1>ZBtcR%lvIhp>u%Q#eyoSprJuR&n(PhW*osvQRWrD#gI*D2a)ZC@1_c(>m#H3ip z%l*eZ_lqI=;_#mcuP1jd&DvR<{-$@(5+^v#HB;Di=g^{1^&L>?H!?xmEop5G+@604> z1tW`Y3k<*?F?4cpvvRtR5%lw&t>P~)PC7`ruqwutU0R(I;%gCp`Rg!TeLLn2jwAJw zfM0l*0g>R>{sUAFTyJM&-cMV3*+}zN-(5Nx-=NLkeUl|N@YeZ}K4OVM%7-;+pfJ?v z0lcT_ejiQ0Z*vs41lEg0@{->1QVL)A8enkhWH8*})Ij+!Rq|D`P6zsIMNNHmO7_*j z&GzDHiAEqY&|1){w<}gCXcl;S_uMvfGC;sOTNPTb@M-<6_MK=fjY(x$KF9Y9A3YqL z*aw}pD=$F|a|Uy@6}us8=F(de!I9PvOBe5$OuPT{*?7rw_@TezKuzDahkr|@`*s*S z4p`G?a1xy1+LnN0%JmgiLYxR;9dvPHV?i!&a1AMY`B(7tP&uNQNC}@TM>WbIf!MQW z)|ZU0Kee%d^__U+XAjbi+Hh~(tH$)=o<(`;y)+!Tfj{}wn#6}hlF5ukCZlGCFFziY zN+iWGg6|i3kENK@r6u+2V6fe&QHa6k_tI=k?Y+ti#dmM|3hl4`2y5)t-!W16G`%fo z7d49Pc1Mq9foZpVx~YN$*1gc!zIrmfJairYC^p^aL7Yg_W9kY!28wct5Ow2@Gk$lFCiA2$&C3Kh;pBqYba zJ{al_ok^?SN@vmifZ*7;mzQ0*JZv_%>BG8gvM+jeLU?Ajdxr@L?(OcTt$)T+a`=zf zv~_aLxq8hjN9>10loVrG{9goMTg8UsXD54#1C?@llKVM-JSF!ekg zBBd1nZgIOuspM@AobX~a^ru)`TOgeGW+RtPK%m5X{($bsv&ab$r7@qrS#`KUTUqqx zWtn&euJ2+|U0NC@DkflR1cSjyr)TRY@b2CjF2|G@It090tn;7-S&`mn*%w3BJrCS) zVtbEvk$tZmd&(H!C^kQh=JnI%WoH|swFSHvN_vMOO0`Ai|L$J_Tn406&p1Q-`Ltm1 znh1PKQ5K=va8Xpu_0mNUhgYMXnj{{ zmz*8BnQdgHLvn}{q`QB=tj+NZI@ybDlI0%r)+^mZ-nxIP{>VCv0~JorAsf}cizy7c z4e1)jQ3;k#4~~mdF?}O{-|^*I^!a{w|7SLt&gU4SOpA+i>3MTBo_kul6^BU%i;fGa ztjr(@`;eF~hpq-1593rB)*%ZA4r9CR)js@>YU{lb3WeOLOETXrUfT|st!leUDoSP{ zdj(kWJ@(*GCk4caek+s5Hq8Mf?<)g}rOS&1c|)f0aBzxu%c_l65qoro`3R53(LjFQ z;Nvv%s=3D|4T_A45=P5X8SIUm$re>eZnw6X)XLK`MmnrB0Bs-cyN1qE?~uqUzUAGZ=H{^&tswOXRnjJed|#Ff2J`s{fpJUr3jF>KAuejlqfvp`Vs2JMKj_Q-Kp&(4U$=uatbKRsV@-kte9l0-!Qbx`n`2_!ABZh z46!P9t@oOk^!az>!#0v$TuJLUd?Zy8bj8N#GsIWgsNBxM-q7l)#j~$;BK7+5cDAcJ zj0!c$aFy`%&--$7S&$=$o-zjz+0;_;#b+#?fu_bg2ePiD(GP3)wn(vY-`)>C6xuE( zN6|dR?pZOJ?$ZG;yk;q(4;==^8D|t+J+B#dPfS&>w$xbl9RCnBSnH_jUG}pJT@6bI ze+v?;Hm|g*5=zn}>dDG7^;*niMXM>K^3853oM2nx@dw!9n5t!O976<-T9@5$Xik#_ z+F1`srPJsW9FuKi-`;hIlLH8CU^0@jAZ{Yg=SY7z^;kn@$ zy`LNr(z_O?_(|Ne6J_k72%2jN+TNz3fT*?ht7ADexGfg39UHf!-B-U|i2&bPhxwCU&T4UMP>;_)Ce5eu~7@ zUh!(e+H>GMob&qU>J)?_bdyg=2wA1Qh-I@5RNat|Pc=%|w`2rqV=v2_A_>1a_Cm zr8n;govX|A zxAHiTg(5!p*Nj{Dg<(FKHv;u6giA6zX~I^ep4|wgExG9QEOfAq{pxw{WOD)Ybx~BQ z5HYtlZytF2I~S>t6Z*&s3g9$M`{`IhZGs$3=zxbN$i^1$7ES@4#9B;}OY4{M-z)v; zpK`2KlxXzgx{uKUjboF@Qa*Z!mk%|HJ^|v zmD4E~e!Baew#{T^5|j4zd-jENR?-^M6L$vC_cBlf-*4D3=!%RlXRr%I@)}1zv~9Z3 zZ3gpOzo5uCyQ5rXZfi#(W18eIYx;=B`)86>I-4L)_FYAdyG&(Mi*i$9aepMdi4bVQ zfL=2JS&w3Hf)3xeYJ?RH8CT!ROvh33=))}4q)XY$;^iJ{qGNoZj>ewP6s5P}o9HA4DgKz{vj z6f`B&&Q4oV!ZR zxsv9Z>z-FCY5!UkOwmqp8=QsT`o)K48L0Pv;fx_a2r_|elIOPTrDz#k0Rd8CzTt=@7HfOF;_ z0p64I0qy9q-}YbAT~bMdhMPs_Rq2hX#YRZ}3(mWFh3|Kn^y-$6jlwRO$i|V2b5m2a z8>I@f3vS)v_`V1$*cbO(l~0)Y$k*z%PJL#pMIU7;99o|KQGCS65YT_RT?GD(v95HQ zt?cH77tkNf+)rdarF+6T)^$2o0aJN0qHO0X&3y?jHaY}Wfa&vTe0=Ab62^yS|_OFRUEMkc`5$mphI}FpqG7mRO^%T+iT}K3P z)ZSLwz|G}r?5Ein+{h!|sto+w10vJfCLsUrsOb!`Pt$?NfooG>e%a)tcUn3JS_J# zXG(cjU1@O1Zt6*CzQ4@Zqh4>Bkl62dp&r!Ch>)6rx#5FXy@m&8jt+MY-%%Ru&vi3> z(_Syb+lvvA4fd>P|Bt_p{k!nVED8EN3KtthUyO6u!{>$jEk_EsvJ;Z^MDlHkLxwnH zA;gNf_AQPFU4e)8Rp=O1Fgs9`K4@4Rt*(KS$f-&c|n$R9<6 zV}Tq~=g+C#?m_ho*YeW`&(9`+?jZx>ZXV659UNMw(BNCWqa`2Qizz(xPkmw!uQL}KJG z0Gb)~xeL5{d2%ZYiWxyUMz_^%U_7E3yh_d@u)A+HpBnNdLG8oQL`i-!ea(8&&#!a~ zm}WFw-`g@|7<40T%7xKG9$7c@L?K`}d#R3o5{TP^$T7Tk(LjdL`wPljs9t3bsO3Zj zRUSfjqQAaBf)N@>Ra7fo;I^9Gv7)Efw;e~sVs9I4P0oBTXwQqtP0?f@sSIuHUv<1K z=L>B@1np%hu)$l02Gp)`r#`g{55(i*iV4n@>v=KrHuK=H*Wa5CE%)Il2C zsSZ$Ar|jHun|D6fVd}ky11(C-hQ&kuoOQmh8YmDDZ?qs&QVK+;f-`Yj6`pvA!x#Xv zB&Q3l_tt{+&%lS#olI-FR@AIr>fkp?->Y~`mRu!}(M8W;*E8}>PPo?`tu0v^$(erV zf0aoOf2)EUsU$D=L&C!2EjN7N6`{nEGA2ohNz1tp#fb|I>Ai z{X!mT39~sUF_jA^(N{vizP%XvqUH1M3wBvGejyW8Ep;eedWdvLHfnL1(h?ZRa0RX} zx3}VDrRW}o#=SAjW=mH{ru*cz3kP`My~(A6Tj%4)+Zh;7SoFG`#5#SGlL{TR_;=sP z;N*SObn-c}grX*nda~on@I3uCc&K`Cfhl^3npESs!-EAoEwgbcV2P{*17` zyeWz=X3r)ID?qE?;PWOhpEBu_n_-I+Zjl+%Ea_#+i=g!B;jXj?H?oq^i)dlcRcEuR8R&E^GCL{@ehb%f=ndStf zu;@3RD$!5+!UGeS-g;1AtO5$v+cIYuz>VTQpp_s4!k2h zftmGa29tq_AMNjUWc{yXsN+<^F-%0<{)50kxC^KbOtPuk`B`gvRW<mr5C@%Jb=j}oC(B)%+2Bzd}-H#u&#;}LoiO>R zs1y>X;lBm}Ffjcb?dqclR>XfSim_Z zsfCeam70i~k2_+g?z~ATkd?B@hm)H5BDkOVYFqJD$C!dUq;^6+oMLmva;1O-(Zia` zN=qFi+$bx?{(X?39h}Me0Sxc4z(r@7eLQ#*c`mp3Yv64P9N@%7=S4OD*Iy9xfu>L| z^RH;uo&Ds(B&B4yj00J69nf1gsh7ACx%ZL%7tg%k(@h?YykH5 zeAN9fh4jBi!DT{&7}D);3VQA^1G?AO(-C?jsVma*=zU;Y zIlBl;ZR6r0MHmJ(d4ng*2BO)?c|1Z#=b>L2?1*+xAqP z(5w|dTE^=$`@(TJap}7uGri<7?x^y3IWZVGoh?UustPq2SWJ1tLA48u-TY4~-LHmN$oq8p!fF?% z{m)73W9H41-+b2Ia{sI};gQBo`fA`G^KX17v1J?+^5guVtvNj!5iQ9Qe8XpFWks}} z(TdwvzODT?=fUXpFEr)K>6t6jy#sr-n4+)6@n`f-54CA=I+RbRA1%a@=_BrS^Whny zY9(j^?P&j66>%P8cYO`BZIr07AM~XoFr-+5XW>%7G^J;66ck>-{=kXdvJ|&Y23Lrn zGV@0I$y1bYZX~Lh%;k4(+YYpOwu)SR(lU4z%JO80#45o5w~uJ--iFkQk zOIG_Iv2~m<_8-mb*=vH8S2ND88udzrbKuA0vvDBk_+xV~8HOy0pMrER7hyoj4suj# z5T?C69cbOY)8h<0&tUx2%jM?0LO!JhZwR)<@(~rPH|k2kmaR! zy&1*0ufU6Xnv#w%{FmXpaHzc_%vm2xTm65(=4EnXqFxt~F1g5SLkZIz4uX|>f1$P;bHBg*8;W82u6~m$v6OR& zpiF}g>GG)sFXY?Qba(5p8CUZ~5TOCbMnp#u3IrUt;4nf=8T>F|W8D^xzUt3pK3f<4 z;jk&PaTf-eTlw{xF>B)|BE{8Xj{=lM61ghbio@pSx;59Yj0T*>p?SyKRi`g+3bukP zuGg@~jfpU{h(A2!ND!8NvCcTl>NjfuM&K2h0$I$s?z-W^{W+38q{+m zJ*znOS8dviIB4J#F?R!az1x&!_5$~XtzzdSydo?-39)>|Usc0_Nam5+>)DOf!T9LU zHlxNrL4F{2!+gwd$6{gU3JoSUXmF$gY~KhBa|1A}$yMO%HykqAocylAPoNO9*3r=c zBVuUPAV!|5~;{Fw0iT5y7M9t?hLHUABxf9;Ks8U*MD35Y_-2GC%(Ff!bPazRc8+=NRY1L+lbzju zJe|c92d*_JLP^Y^3|y5*%yveMlSecnwb+{~ ztNzKT_+<0hrzEL^n=RoW$LwVdZj@5ccR{x`v!!(v(Qq*xuU`nif z7?VkGDU46w!{bU@M`z9ze1ovD;jS={8#MgU(QdLv_**^I;sncG0w1$J`G1%<;rmr5aY^+z>J+GHp0AZ2jqG0vAU{Y4A zj)yCLM`VQuAY9Vqz{?jTz5&~^<928k`I$)V>t*I*5ih#RQVYV8%p%)&Y&b}6G_1{l z^{M7ZYXr`LHsCY0;Tpcr>HS7w8M(OYCSZsM){#F%(6$3ypX1`=ae_EM)Pn#?L!t(P z6Zt>Zn-)p!`wSwjqp+dxr4cymq}r|zejv5TH+RIS@h!vw!fAaqB2y0mFvBK1S`y*m z!QQ%S>+HY{ToW*jh-h<>x`DBpmq<`tz`)bOqDX(%dw$N`ED` zx}2Y%D+-WCTSE6QphfqeL>D^aI*z@dy<+E+ub$p!|?();52GWyUrK^keGur zxNcDKrAhW`S5nK^zFZaEPm)=7@Lw^90}rNyOE5B&=lzy|`6BRg1k;GI{qAW7EsO@E zR|Z~ItHa|`H%t1mf7x3#88`Oh%rcUPP=7}mopjR#J9PfUcQk!w(Bwe{=W~No$A1c6 z*RknRFNH)6{{~W4*VSzdbA1$Ys{^52akXycK}2JDia`KRVq^e7jw*{48fU#qRkCrZ z6v1-^%CF1@B0Dk)I_%Vqhu<2@YZ+I3@9V>)X>`Nk&j&Q>YMa+uy_uuEnTE+b^@mmx zn$xx*PcP1aj|*~mqQ}h#H8&fWz0ysTaQAb9pLK4(*Syw!Jmf_jP7IgOHN}>(3+pAB zTz8l1JK^AoX>js*GoInXn{P)P9gEp!3Iqg$2yMz z{pBBZrU$%Uok0FA-ulJELa-_6i7(+Y!bZ1j*_oKE%_5so1%#P6`E7b^1zqBFMZ*4s zC$hWI;XFseuO>^m3!McB4?x0SvOh@iz8QHD67;SXrxY;6Mtw}<3f9XIL%DiHR$S`I z@yS9X8DV^=-$p&AR-LNhJq6tw)N*9-s`vY)dGfOC4v2>N7$*AqBG&p6g0}8S3Lw;( z7?U%GhVoT-13cE}{ySV`GlV0sQB!F^*F(&q(tm2>_lp77y=WMQ)H^r%6lo=;VSC7j zULe|2^XH51&2NWNA2Ha{arnGvr^s!lL$AKyG&9Sy~6&adk1`9xx9r8TL|HMci9seJ$2Bn^kxUYhxE6VB;U5=>7Y2DB-|wkBXJU3rZZ2&H&c#BJTzNB%kR%8K`U!$G>co(Lp4@ zGj|RGGw8vHu<>}{L2_H*10+F&;b9}zxYU9`J@%;vLZV;=nv*-6eC`{V9$+9|hGKLf zGlD^3AiVu9{&CCW2LMScHeXe&R`_Af8fC>jiDP`qemor+(JSF$D zrHxUf{+64-0x-L#4gQoLTJzyxUIWxRXzC`}rT#LEYY7Z%9+ruQJfItr1 zZkJE(U;LKeO8bA>TQS1qVyBC>blC_7pJ3Nn+uAt!M)FwcBJ=3I zH|yYAq1^=1ER$u&L5?vKT(URR4^*INY0A!~q7}fR$w8p3&<0=GSE%I~{L@bs-bj;m z*+%1+*;kBIVXsAu+Ov@q#;Un7|J0#6k+v;@3cOVPNg1cDoRBVcYHI2ea$6ZpTe>%% z5zvxe&0uETE;g5|DHu!e5T~n1N7o`nPZeR><33yGg@h?Q+HOZ@KYP#p= zFvdWA_+$#y<^+KiAl9%pPhnbYk1|Tc2Na7LIh#abmON4$|j56pS7#4QyNfkaNtM2L<t z&IS1g{`q)@*^Ae9PJt5Hy+o!jz{1gP{!1wOj7bLUP+4B!Em#-XM551TjaQ;Bdcc^BHMnooa%0j z{pWjNMjF$Pa}|*{5Wjpk>Ynjt&da;mP>8AzW}KnLi#{AIz5AFw5b`uQK(7*GZ7l+ zkQ~g^141^FIu^Pgx1(LerpqAN_JFc@>k+_zS9Ev3xBy|o=-3BlX<8*)?y?8Wes}@7 zsT>F{!*q>iBXdUyvQBnBA;?C}mHA@G@NGSh$Skz4gE6qih5ZY65f3)t)1E_3bH*>_KXgOt&` zo^2|MlY=1U0){2>a`yP3gR!)%Ir*S~T6-f38u$`5rfP0{flUsev0Cu89!QTscN=}V z)Pwy#vO)z6L=2Hak{F4`fynr#g^#ZyEB>uN0#FEkHhjbaEqHLn*b~UNSfylxQa)NF zIVQ7HM<#QK7m!{FVW0XAK7iqkK^ZUTpxbsE8ygYyl}2bn3W)~Q)&fs~ow(;_27|IS z-Ud-_4c%kTX)Rxm)RzjgJuZ=LH(j2ntrp~8$3FF*IpB1!e; z@B&`i>92J_!$oHP9YtLnyZbDXmcuT%mJ0U(>A*@fF{+<4VLSFb*mZ;=Qi}RpK9xw1 zIEo((jOT69)tvsOW-qpXN{ILmFBmA6Rc)e%s-l!%?a1>LWgSU9`$x_JuxL3OUC5Eu zCb-N@L6pQF|AJ)?VE?!OrYM9-QG0OrIKYe53OXA-mvxXvrYr7Qs6Rk_M ztAyCe)*7^l!@*kQlFuS(L+L!u=luTuect=~exB$1e4poQ`z>%NPu-AzS_w%)z09R^ z^DMm&D{58JoW-ZWVE=JxxFPLU1W~cgQSO0I7+zLk*e+Ec7`x9o|GJLzufpt_^q@f- z$Ug7zxj26K)z$dAxdXTHV7CK8Lw)Vp)M;HsdU&lzN^inG14_?*GPElI`br zs*|{h2k{V&n^WNj!|0ceJw^(hfxA-MC~`nlJ^oAZ@L@*ANz1GZRo+G|@m+J!^lJx~ zOLgEhQD2p1u(*xnLkgHrwJ&EzOz4XF0WijD6aSVXkQs|ae@W!&(Y4?2*GQNgx`Bb_ ziY1kX8}$H?0q2NpVk`sfLjBxbnM0Mq+nf{#F!_GnWeKLRsQZparMjE_W(f zr0U6}R=yodDWAj6+JQQr7khAt^>ypDGag}3?Wy);HchiltE0DqLrSOo<_ie#$kfWi zEQRze7d%Qx1C7J`HnS5rmI+;rk#)Kh^i+H-bfW3*sHfyVNvpjAWdqNoJZhW)fF3J~ zjF2}Vb>JhHw2e_JIbO1xCj@+^JM-C_3gWHd6+XT1o z1t0|Z)VOcB0o$#J!}-*>c3@b1-1@TV!o)dF!)S|}3XJ_mBCSSKe8~r)hK>g3Zx{0Q zC2jkpc-L4nrVg~U8h3)cyay#zOpz#82CkHM^9YF0bnDiw091}yK8w~N9anAFa#%gqqspO{c&AH8fF&vp9v+a$)%Lu~dPPO&nM5U8&P~@W=(cTp z<)v`AM@BjCG00k?KId9?o(2>1F+nwr|UArP)DG`SUoU4xD(=qk5!!# zcFul|M*0KP>wGm#J+@<_YjRm{J4Y#XiWDk_)6m+SxVDgVe=zaD?B~8NsUVr3a!E4lL}JbtxV-Bp}q^^a#~66*E@&owp|6? zjF{D4>k`jWn+7*;{yQ+XaA5ZrWto=kSWXoRy__lsWdsKc-;bQzVtb+v>U}8xw_j9=6nkA7?g{UWXT#)JlNi74c$J& zi`;pIacSPP{2pQZC;zs@e-;jgkHrhIlY5XUR#jP(t-{TXd0IkkG2W%n?#kYz6u@M9 z&K~_Sui1%CN%q+i4zZFBep#s${V~OerIlOz7WPYC9T3~Cu!Ey)tO+U>}QiK1xN8m zvDFf5l&nDt_cW46niyJ?SPLSN-mLSN8GphxCuRTgruMjRuz7JIRa9s$Dy$K<@~AB; z9X_mRv(%c^kKdNA>Whw9aP`^j5<6`#^)P&SUi#e~T*dF-JUCf`yTkD2yE}QSMTw1V zW@QndDso3EWg9<8%g1gVL6^~n&8$5d<25LVj%5mkWkO*~v4=nboGbNR2Bf{Y1jL|R z+}`RF{{FeCok2>R+MZDJnHX^lrgYsH>uW4yh2~3FJw!#SzEwm|FWm_AsxST)6cIW5 zwN8TgwltH-0qPH4yiVbIBW3jY_jV%35q%yAi_MA|KhyN@4FzqxVc8P`17`SFo#vkn TLQrgPHGkj5tRC0-X0zM literal 0 HcmV?d00001 diff --git a/neoforge/loader/src/main/resources/pack.mcmeta b/neoforge/loader/src/main/resources/pack.mcmeta new file mode 100644 index 000000000..f6749d3ff --- /dev/null +++ b/neoforge/loader/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "LuckPerms resources", + "pack_format": 9 + } +} \ No newline at end of file diff --git a/neoforge/neoforge-api/build.gradle b/neoforge/neoforge-api/build.gradle new file mode 100644 index 000000000..b231d62e4 --- /dev/null +++ b/neoforge/neoforge-api/build.gradle @@ -0,0 +1,16 @@ +plugins { + alias(libs.plugins.moddevgradle) +} + +sourceCompatibility = 17 +targetCompatibility = 21 + +neoForge { + version = project.neoForgeVersion + + validateAccessTransformers = true +} + +dependencies { + implementation project(':api') +} diff --git a/neoforge/neoforge-api/src/main/java/me/lucko/luckperms/neoforge/capabilities/UserCapability.java b/neoforge/neoforge-api/src/main/java/me/lucko/luckperms/neoforge/capabilities/UserCapability.java new file mode 100644 index 000000000..5fbdb85b2 --- /dev/null +++ b/neoforge/neoforge-api/src/main/java/me/lucko/luckperms/neoforge/capabilities/UserCapability.java @@ -0,0 +1,83 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge.capabilities; + +import net.luckperms.api.query.QueryOptions; +import net.luckperms.api.util.Tristate; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.neoforged.neoforge.capabilities.EntityCapability; + +/** + * A NeoForge {@link EntityCapability} that attaches LuckPerms functionality onto {@link ServerPlayer}s. + */ +public interface UserCapability { + + /** + * The identifier used for the capability + */ + ResourceLocation IDENTIFIER = ResourceLocation.fromNamespaceAndPath("luckperms", "user"); + + /** + * The capability instance. + */ + EntityCapability CAPABILITY = EntityCapability.createVoid(IDENTIFIER, UserCapability.class); + + /** + * Checks for a permission. + * + * @param permission the permission + * @return the result + */ + default boolean hasPermission(String permission) { + return checkPermission(permission).asBoolean(); + } + + /** + * Runs a permission check. + * + * @param permission the permission + * @return the result + */ + Tristate checkPermission(String permission); + + /** + * Runs a permission check. + * + * @param permission the permission + * @param queryOptions the query options + * @return the result + */ + Tristate checkPermission(String permission, QueryOptions queryOptions); + + /** + * Gets the user's currently query options. + * + * @return the current query options for the user + */ + QueryOptions getQueryOptions(); + +} diff --git a/neoforge/src/main/java/me/lucko/luckperms/neoforge/LPNeoForgeBootstrap.java b/neoforge/src/main/java/me/lucko/luckperms/neoforge/LPNeoForgeBootstrap.java new file mode 100644 index 000000000..f1001cf0e --- /dev/null +++ b/neoforge/src/main/java/me/lucko/luckperms/neoforge/LPNeoForgeBootstrap.java @@ -0,0 +1,287 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge; + +import com.mojang.authlib.GameProfile; +import me.lucko.luckperms.common.loader.LoaderBootstrap; +import me.lucko.luckperms.common.plugin.bootstrap.BootstrappedWithLoader; +import me.lucko.luckperms.common.plugin.bootstrap.LuckPermsBootstrap; +import me.lucko.luckperms.common.plugin.classpath.ClassPathAppender; +import me.lucko.luckperms.common.plugin.classpath.JarInJarClassPathAppender; +import me.lucko.luckperms.common.plugin.logging.Log4jPluginLogger; +import me.lucko.luckperms.common.plugin.logging.PluginLogger; +import me.lucko.luckperms.common.plugin.scheduler.SchedulerAdapter; +import me.lucko.luckperms.neoforge.util.NeoForgeEventBusFacade; +import net.luckperms.api.platform.Platform; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.PlayerList; +import net.neoforged.bus.api.EventPriority; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.ModContainer; +import net.neoforged.fml.ModList; +import net.neoforged.fml.loading.FMLPaths; +import net.neoforged.neoforge.event.server.ServerAboutToStartEvent; +import net.neoforged.neoforge.event.server.ServerStoppingEvent; +import net.neoforged.neoforgespi.language.IModInfo; +import org.apache.logging.log4j.LogManager; +import org.apache.maven.artifact.versioning.ArtifactVersion; + +import java.nio.file.Path; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.function.Supplier; + +/** + * Bootstrap plugin for LuckPerms running on Forge. + */ +public final class LPNeoForgeBootstrap implements LuckPermsBootstrap, LoaderBootstrap, BootstrappedWithLoader { + public static final String ID = "luckperms"; + + /** + * The plugin loader + */ + private final Supplier loader; + + /** + * The plugin logger + */ + private final PluginLogger logger; + + /** + * A scheduler adapter for the platform + */ + private final SchedulerAdapter schedulerAdapter; + + /** + * The plugin class path appender + */ + private final ClassPathAppender classPathAppender; + + /** + * A facade for the forge event bus, compatible with LP's jar-in-jar packaging + */ + private final NeoForgeEventBusFacade forgeEventBus; + + /** + * The plugin instance + */ + private final LPNeoForgePlugin plugin; + + /** + * The time when the plugin was enabled + */ + private Instant startTime; + + // load/enable latches + private final CountDownLatch loadLatch = new CountDownLatch(1); + private final CountDownLatch enableLatch = new CountDownLatch(1); + + /** + * The Minecraft server instance + */ + private MinecraftServer server; + + public LPNeoForgeBootstrap(Supplier loader) { + this.loader = loader; + this.logger = new Log4jPluginLogger(LogManager.getLogger(LPNeoForgeBootstrap.ID)); + this.schedulerAdapter = new NeoForgeSchedulerAdapter(this); + this.classPathAppender = new JarInJarClassPathAppender(getClass().getClassLoader()); + this.forgeEventBus = new NeoForgeEventBusFacade(); + this.plugin = new LPNeoForgePlugin(this); + } + + // provide adapters + + @Override + public Object getLoader() { + return this.loader; + } + + @Override + public PluginLogger getPluginLogger() { + return this.logger; + } + + @Override + public SchedulerAdapter getScheduler() { + return this.schedulerAdapter; + } + + @Override + public ClassPathAppender getClassPathAppender() { + return this.classPathAppender; + } + + public void registerListeners(Object target) { + this.forgeEventBus.register(target); + } + + // lifecycle + + @Override + public void onLoad() { // called by the loader on FMLCommonSetupEvent + this.startTime = Instant.now(); + try { + this.plugin.load(); + } finally { + this.loadLatch.countDown(); + } + + this.forgeEventBus.register(this); + this.plugin.registerEarlyListeners(); + } + + @SubscribeEvent(priority = EventPriority.HIGHEST) + public void onServerAboutToStart(ServerAboutToStartEvent event) { + this.server = event.getServer(); + try { + this.plugin.enable(); + } finally { + this.enableLatch.countDown(); + } + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void onServerStopping(ServerStoppingEvent event) { + this.plugin.disable(); + this.forgeEventBus.unregisterAll(); + this.server = null; + } + + @Override + public CountDownLatch getLoadLatch() { + return this.loadLatch; + } + + @Override + public CountDownLatch getEnableLatch() { + return this.enableLatch; + } + + // MinecraftServer singleton getter + + public Optional getServer() { + return Optional.ofNullable(this.server); + } + + // provide information about the plugin + + @Override + public String getVersion() { + return "@version@"; + } + + @Override + public Instant getStartupTime() { + return this.startTime; + } + + // provide information about the platform + + @Override + public Platform.Type getType() { + return Platform.Type.NEOFORGE; + } + + @Override + public String getServerBrand() { + return ModList.get().getModContainerById("neoforge") + .map(ModContainer::getModInfo) + .map(IModInfo::getDisplayName) + .orElse("null"); + } + + @Override + public String getServerVersion() { + String forgeVersion = ModList.get().getModContainerById("neoforge") + .map(ModContainer::getModInfo) + .map(IModInfo::getVersion) + .map(ArtifactVersion::toString) + .orElse("null"); + + return getServer().map(MinecraftServer::getServerVersion).orElse("null") + "-" + forgeVersion; + } + + @Override + public Path getDataDirectory() { + return FMLPaths.CONFIGDIR.get().resolve(LPNeoForgeBootstrap.ID).toAbsolutePath(); + } + + @Override + public Optional getPlayer(UUID uniqueId) { + return getServer().map(MinecraftServer::getPlayerList).map(playerList -> playerList.getPlayer(uniqueId)); + } + + @Override + public Optional lookupUniqueId(String username) { + return getServer().map(MinecraftServer::getProfileCache).flatMap(profileCache -> profileCache.get(username)).map(GameProfile::getId); + } + + @Override + public Optional 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 getPlayerList() { + return getServer().map(MinecraftServer::getPlayerList).map(PlayerList::getPlayers).map(players -> { + List list = new ArrayList<>(players.size()); + for (ServerPlayer player : players) { + list.add(player.getGameProfile().getName()); + } + return list; + }).orElse(Collections.emptyList()); + } + + @Override + public Collection getOnlinePlayers() { + return getServer().map(MinecraftServer::getPlayerList).map(PlayerList::getPlayers).map(players -> { + List 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(); + } + +} diff --git a/neoforge/src/main/java/me/lucko/luckperms/neoforge/LPNeoForgePlugin.java b/neoforge/src/main/java/me/lucko/luckperms/neoforge/LPNeoForgePlugin.java new file mode 100644 index 000000000..453614480 --- /dev/null +++ b/neoforge/src/main/java/me/lucko/luckperms/neoforge/LPNeoForgePlugin.java @@ -0,0 +1,251 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge; + +import me.lucko.luckperms.common.api.LuckPermsApiProvider; +import me.lucko.luckperms.common.calculator.CalculatorFactory; +import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.common.config.generic.adapter.ConfigurationAdapter; +import me.lucko.luckperms.common.dependencies.Dependency; +import me.lucko.luckperms.common.event.AbstractEventBus; +import me.lucko.luckperms.common.locale.TranslationManager; +import me.lucko.luckperms.common.messaging.MessagingFactory; +import me.lucko.luckperms.common.model.User; +import me.lucko.luckperms.common.model.manager.group.StandardGroupManager; +import me.lucko.luckperms.common.model.manager.track.StandardTrackManager; +import me.lucko.luckperms.common.model.manager.user.StandardUserManager; +import me.lucko.luckperms.common.plugin.AbstractLuckPermsPlugin; +import me.lucko.luckperms.common.sender.DummyConsoleSender; +import me.lucko.luckperms.common.sender.Sender; +import me.lucko.luckperms.neoforge.calculator.NeoForgeCalculatorFactory; +import me.lucko.luckperms.neoforge.capabilities.UserCapabilityListener; +import me.lucko.luckperms.neoforge.context.NeoForgeContextManager; +import me.lucko.luckperms.neoforge.context.NeoForgePlayerCalculator; +import me.lucko.luckperms.neoforge.listeners.NeoForgeAutoOpListener; +import me.lucko.luckperms.neoforge.listeners.NeoForgeCommandListUpdater; +import me.lucko.luckperms.neoforge.listeners.NeoForgeConnectionListener; +import me.lucko.luckperms.neoforge.listeners.NeoForgePlatformListener; +import me.lucko.luckperms.neoforge.messaging.NeoForgeMessagingFactory; +import me.lucko.luckperms.neoforge.messaging.PluginMessageMessenger; +import me.lucko.luckperms.neoforge.service.NeoForgePermissionHandlerListener; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import net.luckperms.api.LuckPerms; +import net.luckperms.api.query.QueryOptions; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.players.PlayerList; +import net.neoforged.fml.ModContainer; + +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; + +/** + * LuckPerms implementation for Forge. + */ +public class LPNeoForgePlugin extends AbstractLuckPermsPlugin { + private final LPNeoForgeBootstrap bootstrap; + + private NeoForgeSenderFactory senderFactory; + private NeoForgeConnectionListener connectionListener; + private NeoForgeCommandExecutor commandManager; + private StandardUserManager userManager; + private StandardGroupManager groupManager; + private StandardTrackManager trackManager; + private NeoForgeContextManager contextManager; + + public LPNeoForgePlugin(LPNeoForgeBootstrap bootstrap) { + this.bootstrap = bootstrap; + } + + @Override + public LPNeoForgeBootstrap getBootstrap() { + return this.bootstrap; + } + + protected void registerEarlyListeners() { + this.connectionListener = new NeoForgeConnectionListener(this); + this.bootstrap.registerListeners(this.connectionListener); + + NeoForgePlatformListener platformListener = new NeoForgePlatformListener(this); + this.bootstrap.registerListeners(platformListener); + + UserCapabilityListener userCapabilityListener = new UserCapabilityListener(); + this.bootstrap.registerListeners(userCapabilityListener); + + NeoForgePermissionHandlerListener permissionHandlerListener = new NeoForgePermissionHandlerListener(this); + this.bootstrap.registerListeners(permissionHandlerListener); + + this.commandManager = new NeoForgeCommandExecutor(this); + this.bootstrap.registerListeners(this.commandManager); + + PluginMessageMessenger.registerChannel(); + } + + @Override + protected void setupSenderFactory() { + this.senderFactory = new NeoForgeSenderFactory(this); + } + + @Override + protected Set getGlobalDependencies() { + Set dependencies = super.getGlobalDependencies(); + dependencies.add(Dependency.CONFIGURATE_CORE); + dependencies.add(Dependency.CONFIGURATE_HOCON); + dependencies.add(Dependency.HOCON_CONFIG); + return dependencies; + } + + @Override + protected ConfigurationAdapter provideConfigurationAdapter() { + return new NeoForgeConfigAdapter(this, resolveConfig("luckperms.conf")); + } + + @Override + protected void registerPlatformListeners() { + // Too late for Forge, registered in #registerEarlyListeners + } + + @Override + protected MessagingFactory provideMessagingFactory() { + return new NeoForgeMessagingFactory(this); + } + + @Override + protected void registerCommands() { + // Too late for Forge, registered in #registerEarlyListeners + } + + @Override + protected void setupManagers() { + this.userManager = new StandardUserManager(this); + this.groupManager = new StandardGroupManager(this); + this.trackManager = new StandardTrackManager(this); + } + + @Override + protected CalculatorFactory provideCalculatorFactory() { + return new NeoForgeCalculatorFactory(this); + } + + @Override + protected void setupContextManager() { + this.contextManager = new NeoForgeContextManager(this); + + NeoForgePlayerCalculator playerCalculator = new NeoForgePlayerCalculator(this, getConfiguration().get(ConfigKeys.DISABLED_CONTEXTS)); + this.bootstrap.registerListeners(playerCalculator); + this.contextManager.registerCalculator(playerCalculator); + } + + @Override + protected void setupPlatformHooks() { + } + + @Override + protected AbstractEventBus provideEventBus(LuckPermsApiProvider provider) { + return new NeoForgeEventBus(this, provider); + } + + @Override + protected void registerApiOnPlatform(LuckPerms api) { + } + + @Override + protected void performFinalSetup() { + // register autoop listener + if (getConfiguration().get(ConfigKeys.AUTO_OP)) { + getApiProvider().getEventBus().subscribe(new NeoForgeAutoOpListener(this)); + } + + // register forge command list updater + if (getConfiguration().get(ConfigKeys.UPDATE_CLIENT_COMMAND_LIST)) { + getApiProvider().getEventBus().subscribe(new NeoForgeCommandListUpdater(this)); + } + } + + @Override + public Optional getQueryOptionsForUser(User user) { + return this.bootstrap.getPlayer(user.getUniqueId()).map(player -> this.contextManager.getQueryOptions(player)); + } + + @Override + public Stream getOnlineSenders() { + return Stream.concat( + Stream.of(getConsoleSender()), + this.bootstrap.getServer() + .map(MinecraftServer::getPlayerList) + .map(PlayerList::getPlayers) + .map(players -> players.stream().map(player -> this.senderFactory.wrap(player.createCommandSourceStack()))).orElseGet(Stream::empty) + ); + } + + @Override + public Sender getConsoleSender() { + return this.bootstrap.getServer() + .map(server -> this.senderFactory.wrap(server.createCommandSourceStack())) + .orElseGet(() -> new DummyConsoleSender(this) { + @Override + public void sendMessage(Component message) { + LPNeoForgePlugin.this.bootstrap.getPluginLogger().info(PlainTextComponentSerializer.plainText().serialize(TranslationManager.render(message))); + } + }); + } + + public NeoForgeSenderFactory getSenderFactory() { + return this.senderFactory; + } + + @Override + public NeoForgeConnectionListener getConnectionListener() { + return this.connectionListener; + } + + @Override + public NeoForgeCommandExecutor getCommandManager() { + return this.commandManager; + } + + @Override + public StandardUserManager getUserManager() { + return this.userManager; + } + + @Override + public StandardGroupManager getGroupManager() { + return this.groupManager; + } + + @Override + public StandardTrackManager getTrackManager() { + return this.trackManager; + } + + @Override + public NeoForgeContextManager getContextManager() { + return this.contextManager; + } + +} diff --git a/neoforge/src/main/java/me/lucko/luckperms/neoforge/NeoForgeCommandExecutor.java b/neoforge/src/main/java/me/lucko/luckperms/neoforge/NeoForgeCommandExecutor.java new file mode 100644 index 000000000..0a5ef3767 --- /dev/null +++ b/neoforge/src/main/java/me/lucko/luckperms/neoforge/NeoForgeCommandExecutor.java @@ -0,0 +1,108 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.ArgumentCommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; +import me.lucko.luckperms.common.command.BrigadierCommandExecutor; +import me.lucko.luckperms.common.sender.Sender; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.server.level.ServerPlayer; + +import java.util.List; +import java.util.ListIterator; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.event.RegisterCommandsEvent; + +public class NeoForgeCommandExecutor extends BrigadierCommandExecutor { + + private final LPNeoForgePlugin plugin; + + public NeoForgeCommandExecutor(LPNeoForgePlugin plugin) { + super(plugin); + this.plugin = plugin; + } + + @SubscribeEvent + public void onRegisterCommands(RegisterCommandsEvent event) { + for (String alias : COMMAND_ALIASES) { + LiteralCommandNode command = Commands.literal(alias).executes(this).build(); + ArgumentCommandNode 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 resolveSelectors(CommandSourceStack source, List args) { + // usage of @ selectors requires at least level 2 permission + CommandSourceStack atAllowedSource = source.hasPermission(2) ? source : source.withPermission(2); + for (ListIterator it = args.listIterator(); it.hasNext(); ) { + String arg = it.next(); + if (arg.isEmpty() || arg.charAt(0) != '@') { + continue; + } + + List 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; + } + +} diff --git a/neoforge/src/main/java/me/lucko/luckperms/neoforge/NeoForgeConfigAdapter.java b/neoforge/src/main/java/me/lucko/luckperms/neoforge/NeoForgeConfigAdapter.java new file mode 100644 index 000000000..5cb671840 --- /dev/null +++ b/neoforge/src/main/java/me/lucko/luckperms/neoforge/NeoForgeConfigAdapter.java @@ -0,0 +1,46 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge; + +import me.lucko.luckperms.common.config.generic.adapter.ConfigurateConfigAdapter; +import me.lucko.luckperms.common.plugin.LuckPermsPlugin; +import ninja.leaping.configurate.ConfigurationNode; +import ninja.leaping.configurate.hocon.HoconConfigurationLoader; +import ninja.leaping.configurate.loader.ConfigurationLoader; + +import java.nio.file.Path; + +public class NeoForgeConfigAdapter extends ConfigurateConfigAdapter { + public NeoForgeConfigAdapter(LuckPermsPlugin plugin, Path path) { + super(plugin, path); + } + + @Override + protected ConfigurationLoader createLoader(Path path) { + return HoconConfigurationLoader.builder().setPath(path).build(); + } + +} diff --git a/neoforge/src/main/java/me/lucko/luckperms/neoforge/NeoForgeEventBus.java b/neoforge/src/main/java/me/lucko/luckperms/neoforge/NeoForgeEventBus.java new file mode 100644 index 000000000..9936ac8fc --- /dev/null +++ b/neoforge/src/main/java/me/lucko/luckperms/neoforge/NeoForgeEventBus.java @@ -0,0 +1,47 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge; + +import me.lucko.luckperms.common.api.LuckPermsApiProvider; +import me.lucko.luckperms.common.event.AbstractEventBus; +import me.lucko.luckperms.common.plugin.LuckPermsPlugin; +import net.neoforged.fml.ModContainer; + +public class NeoForgeEventBus extends AbstractEventBus { + public NeoForgeEventBus(LuckPermsPlugin plugin, LuckPermsApiProvider apiProvider) { + super(plugin, apiProvider); + } + + @Override + protected ModContainer checkPlugin(Object modContainer) throws IllegalArgumentException { + if (modContainer instanceof ModContainer container) { + return container; + } + + throw new IllegalArgumentException("Object " + modContainer + " (" + modContainer.getClass().getName() + ") is not a ModContainer."); + } + +} diff --git a/neoforge/src/main/java/me/lucko/luckperms/neoforge/NeoForgeSchedulerAdapter.java b/neoforge/src/main/java/me/lucko/luckperms/neoforge/NeoForgeSchedulerAdapter.java new file mode 100644 index 000000000..4a257f700 --- /dev/null +++ b/neoforge/src/main/java/me/lucko/luckperms/neoforge/NeoForgeSchedulerAdapter.java @@ -0,0 +1,45 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge; + +import me.lucko.luckperms.common.plugin.scheduler.AbstractJavaScheduler; + +import java.util.concurrent.Executor; + +public class NeoForgeSchedulerAdapter extends AbstractJavaScheduler { + private final Executor sync; + + public NeoForgeSchedulerAdapter(LPNeoForgeBootstrap bootstrap) { + super(bootstrap); + this.sync = r -> bootstrap.getServer().orElseThrow(() -> new IllegalStateException("Server not ready")).executeBlocking(r); + } + + @Override + public Executor sync() { + return this.sync; + } + +} diff --git a/neoforge/src/main/java/me/lucko/luckperms/neoforge/NeoForgeSenderFactory.java b/neoforge/src/main/java/me/lucko/luckperms/neoforge/NeoForgeSenderFactory.java new file mode 100644 index 000000000..25049df2e --- /dev/null +++ b/neoforge/src/main/java/me/lucko/luckperms/neoforge/NeoForgeSenderFactory.java @@ -0,0 +1,124 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge; + +import com.mojang.brigadier.ParseResults; +import me.lucko.luckperms.common.cacheddata.result.TristateResult; +import me.lucko.luckperms.common.locale.TranslationManager; +import me.lucko.luckperms.common.query.QueryOptionsImpl; +import me.lucko.luckperms.common.sender.Sender; +import me.lucko.luckperms.common.sender.SenderFactory; +import me.lucko.luckperms.common.verbose.VerboseCheckTarget; +import me.lucko.luckperms.common.verbose.event.CheckOrigin; +import me.lucko.luckperms.neoforge.capabilities.UserCapability; +import me.lucko.luckperms.neoforge.capabilities.UserCapabilityImpl; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.luckperms.api.util.Tristate; +import net.minecraft.commands.CommandSource; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.core.RegistryAccess; +import net.minecraft.network.chat.Component.Serializer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.rcon.RconConsoleSource; +import net.minecraft.world.entity.player.Player; + +import java.util.Locale; +import java.util.UUID; + +public class NeoForgeSenderFactory extends SenderFactory { + public NeoForgeSenderFactory(LPNeoForgePlugin plugin) { + super(plugin); + } + + @Override + protected UUID getUniqueId(CommandSourceStack commandSource) { + if (commandSource.getEntity() instanceof Player) { + return commandSource.getEntity().getUUID(); + } + return Sender.CONSOLE_UUID; + } + + @Override + protected String getName(CommandSourceStack commandSource) { + if (commandSource.getEntity() instanceof Player) { + return commandSource.getTextName(); + } + return Sender.CONSOLE_NAME; + } + + @Override + protected void sendMessage(CommandSourceStack sender, Component message) { + Locale locale; + if (sender.getEntity() instanceof ServerPlayer) { + ServerPlayer player = (ServerPlayer) sender.getEntity(); + UserCapabilityImpl user = UserCapabilityImpl.get(player); + locale = user.getLocale(player); + } else { + locale = null; + } + + sender.sendSuccess(() -> toNativeText(TranslationManager.render(message, locale)), false); + } + + @Override + protected Tristate getPermissionValue(CommandSourceStack commandSource, String node) { + if (commandSource.getEntity() instanceof ServerPlayer) { + ServerPlayer player = (ServerPlayer) commandSource.getEntity(); + UserCapability user = UserCapabilityImpl.get(player); + return user.checkPermission(node); + } + + VerboseCheckTarget target = VerboseCheckTarget.internal(commandSource.getTextName()); + getPlugin().getVerboseHandler().offerPermissionCheckEvent(CheckOrigin.PLATFORM_API_HAS_PERMISSION, target, QueryOptionsImpl.DEFAULT_CONTEXTUAL, node, TristateResult.UNDEFINED); + getPlugin().getPermissionRegistry().offer(node); + return Tristate.UNDEFINED; + } + + @Override + protected boolean hasPermission(CommandSourceStack commandSource, String node) { + return getPermissionValue(commandSource, node).asBoolean(); + } + + @Override + protected void performCommand(CommandSourceStack sender, String command) { + ParseResults results = sender.getServer().getCommands().getDispatcher().parse(command, sender); + sender.getServer().getCommands().performCommand(results, command); + } + + @Override + protected boolean isConsole(CommandSourceStack sender) { + CommandSource output = sender.source; + return output == sender.getServer() || // Console + output.getClass() == RconConsoleSource.class || // Rcon + (output == CommandSource.NULL && sender.getTextName().equals("")); // Functions + } + + public static net.minecraft.network.chat.Component toNativeText(Component component) { + return Serializer.fromJson(GsonComponentSerializer.gson().serializeToTree(component), RegistryAccess.EMPTY); + } + +} diff --git a/neoforge/src/main/java/me/lucko/luckperms/neoforge/calculator/NeoForgeCalculatorFactory.java b/neoforge/src/main/java/me/lucko/luckperms/neoforge/calculator/NeoForgeCalculatorFactory.java new file mode 100644 index 000000000..afa733b30 --- /dev/null +++ b/neoforge/src/main/java/me/lucko/luckperms/neoforge/calculator/NeoForgeCalculatorFactory.java @@ -0,0 +1,77 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge.calculator; + +import me.lucko.luckperms.common.cacheddata.CacheMetadata; +import me.lucko.luckperms.common.calculator.CalculatorFactory; +import me.lucko.luckperms.common.calculator.PermissionCalculator; +import me.lucko.luckperms.common.calculator.processor.DirectProcessor; +import me.lucko.luckperms.common.calculator.processor.PermissionProcessor; +import me.lucko.luckperms.common.calculator.processor.RegexProcessor; +import me.lucko.luckperms.common.calculator.processor.SpongeWildcardProcessor; +import me.lucko.luckperms.common.calculator.processor.WildcardProcessor; +import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.neoforge.LPNeoForgePlugin; +import me.lucko.luckperms.neoforge.context.NeoForgeContextManager; +import net.luckperms.api.query.QueryOptions; + +import java.util.ArrayList; +import java.util.List; + +public class NeoForgeCalculatorFactory implements CalculatorFactory { + private final LPNeoForgePlugin plugin; + + public NeoForgeCalculatorFactory(LPNeoForgePlugin plugin) { + this.plugin = plugin; + } + + @Override + public PermissionCalculator build(QueryOptions queryOptions, CacheMetadata metadata) { + List processors = new ArrayList<>(5); + + processors.add(new DirectProcessor()); + + if (this.plugin.getConfiguration().get(ConfigKeys.APPLYING_REGEX)) { + processors.add(new RegexProcessor()); + } + + if (this.plugin.getConfiguration().get(ConfigKeys.APPLYING_WILDCARDS)) { + processors.add(new WildcardProcessor()); + } + + if (this.plugin.getConfiguration().get(ConfigKeys.APPLYING_WILDCARDS_SPONGE)) { + processors.add(new SpongeWildcardProcessor()); + } + + boolean integratedOwner = queryOptions.option(NeoForgeContextManager.INTEGRATED_SERVER_OWNER).orElse(false); + if (integratedOwner && this.plugin.getConfiguration().get(ConfigKeys.INTEGRATED_SERVER_OWNER_BYPASSES_CHECKS)) { + processors.add(ServerOwnerProcessor.INSTANCE); + } + + return new PermissionCalculator(this.plugin, metadata, processors); + } + +} diff --git a/neoforge/src/main/java/me/lucko/luckperms/neoforge/calculator/ServerOwnerProcessor.java b/neoforge/src/main/java/me/lucko/luckperms/neoforge/calculator/ServerOwnerProcessor.java new file mode 100644 index 000000000..0773068c7 --- /dev/null +++ b/neoforge/src/main/java/me/lucko/luckperms/neoforge/calculator/ServerOwnerProcessor.java @@ -0,0 +1,51 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge.calculator; + +import me.lucko.luckperms.common.cacheddata.result.TristateResult; +import me.lucko.luckperms.common.calculator.processor.AbstractPermissionProcessor; +import me.lucko.luckperms.common.calculator.processor.PermissionProcessor; +import net.luckperms.api.util.Tristate; + +/** + * Permission processor which is added to the owner of an Integrated server to + * simply return true if no other processors match. + */ +public class ServerOwnerProcessor extends AbstractPermissionProcessor implements PermissionProcessor { + private static final TristateResult TRUE_RESULT = new TristateResult.Factory(ServerOwnerProcessor.class).result(Tristate.TRUE); + + public static final ServerOwnerProcessor INSTANCE = new ServerOwnerProcessor(); + + private ServerOwnerProcessor() { + + } + + @Override + public TristateResult hasPermission(String permission) { + return TRUE_RESULT; + } + +} diff --git a/neoforge/src/main/java/me/lucko/luckperms/neoforge/capabilities/UserCapabilityImpl.java b/neoforge/src/main/java/me/lucko/luckperms/neoforge/capabilities/UserCapabilityImpl.java new file mode 100644 index 000000000..eec08754f --- /dev/null +++ b/neoforge/src/main/java/me/lucko/luckperms/neoforge/capabilities/UserCapabilityImpl.java @@ -0,0 +1,163 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge.capabilities; + +import java.util.Optional; +import me.lucko.luckperms.common.cacheddata.type.PermissionCache; +import me.lucko.luckperms.common.context.manager.QueryOptionsCache; +import me.lucko.luckperms.common.locale.TranslationManager; +import me.lucko.luckperms.common.model.User; +import me.lucko.luckperms.common.verbose.event.CheckOrigin; +import me.lucko.luckperms.neoforge.context.NeoForgeContextManager; +import net.luckperms.api.query.QueryOptions; +import net.luckperms.api.util.Tristate; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Locale; + +public class UserCapabilityImpl implements UserCapability { + + private static Optional getCapability(Player player) { + return Optional.ofNullable(player.getCapability(CAPABILITY)); + } + + /** + * Gets a {@link UserCapability} for a given {@link ServerPlayer}. + * + * @param player the player + * @return the capability + */ + public static @NotNull UserCapabilityImpl get(@NotNull Player player) { + return (UserCapabilityImpl) getCapability(player).orElseThrow(() -> new IllegalStateException("Capability missing for " + player.getUUID())); + } + + /** + * Gets a {@link UserCapability} for a given {@link ServerPlayer}. + * + * @param player the player + * @return the capability, or null + */ + public static @Nullable UserCapabilityImpl getNullable(@NotNull Player player) { + return (UserCapabilityImpl) getCapability(player).orElse(null); + } + + private boolean initialised = false; + private boolean invalidated = false; + + private User user; + private QueryOptionsCache queryOptionsCache; + private String language; + private Locale locale; + + public UserCapabilityImpl() { + + } + + public void initialise(UserCapabilityImpl previous) { + this.user = previous.user; + this.queryOptionsCache = previous.queryOptionsCache; + this.language = previous.language; + this.locale = previous.locale; + this.initialised = true; + } + + public void initialise(User user, ServerPlayer player, NeoForgeContextManager contextManager) { + this.user = user; + this.queryOptionsCache = new QueryOptionsCache<>(player, contextManager); + this.initialised = true; + } + + private void assertInitialised() { + if (!this.initialised) { + throw new IllegalStateException("Capability has not been initialised"); + } + if (this.invalidated) { + throw new IllegalStateException("Capability has been invalidated"); + } + } + + public void invalidate() { + this.invalidated = false; + this.user = null; + this.queryOptionsCache = null; + this.language = null; + this.locale = null; + } + + @Override + public Tristate checkPermission(String permission) { + assertInitialised(); + + if (permission == null) { + throw new NullPointerException("permission"); + } + + return checkPermission(permission, this.queryOptionsCache.getQueryOptions()); + } + + @Override + public Tristate checkPermission(String permission, QueryOptions queryOptions) { + assertInitialised(); + + if (permission == null) { + throw new NullPointerException("permission"); + } + + if (queryOptions == null) { + throw new NullPointerException("queryOptions"); + } + + PermissionCache cache = this.user.getCachedData().getPermissionData(queryOptions); + return cache.checkPermission(permission, CheckOrigin.PLATFORM_API_HAS_PERMISSION).result(); + } + + public User getUser() { + assertInitialised(); + return this.user; + } + + @Override + public QueryOptions getQueryOptions() { + return getQueryOptionsCache().getQueryOptions(); + } + + public QueryOptionsCache 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; + } +} \ No newline at end of file diff --git a/neoforge/src/main/java/me/lucko/luckperms/neoforge/capabilities/UserCapabilityListener.java b/neoforge/src/main/java/me/lucko/luckperms/neoforge/capabilities/UserCapabilityListener.java new file mode 100644 index 000000000..c34f66473 --- /dev/null +++ b/neoforge/src/main/java/me/lucko/luckperms/neoforge/capabilities/UserCapabilityListener.java @@ -0,0 +1,69 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge.capabilities; + +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.player.Player; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent; + +public class UserCapabilityListener { + + @SubscribeEvent + public void onRegisterCapabilities(RegisterCapabilitiesEvent event) { + event.registerEntity( + UserCapability.CAPABILITY, + EntityType.PLAYER, + (player, ctx) -> { + if (!(player instanceof ServerPlayer)) { + // Don't attach to LocalPlayer + return null; + } + return new UserCapabilityImpl(); + } + ); + } + + @SubscribeEvent + public void onPlayerClone(PlayerEvent.Clone event) { + Player previousPlayer = event.getOriginal(); + Player currentPlayer = event.getEntity(); + + try { + UserCapabilityImpl previous = UserCapabilityImpl.get(previousPlayer); + UserCapabilityImpl current = UserCapabilityImpl.get(currentPlayer); + + current.initialise(previous); + previous.invalidate(); + current.getQueryOptionsCache().invalidate(); + } catch (IllegalStateException e) { + // continue on if we cannot copy original data + } + } + +} diff --git a/neoforge/src/main/java/me/lucko/luckperms/neoforge/context/NeoForgeContextManager.java b/neoforge/src/main/java/me/lucko/luckperms/neoforge/context/NeoForgeContextManager.java new file mode 100644 index 000000000..8aca67312 --- /dev/null +++ b/neoforge/src/main/java/me/lucko/luckperms/neoforge/context/NeoForgeContextManager.java @@ -0,0 +1,79 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge.context; + +import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.common.context.manager.ContextManager; +import me.lucko.luckperms.common.context.manager.QueryOptionsCache; +import me.lucko.luckperms.neoforge.LPNeoForgePlugin; +import me.lucko.luckperms.neoforge.capabilities.UserCapabilityImpl; +import net.luckperms.api.context.ImmutableContextSet; +import net.luckperms.api.query.OptionKey; +import net.luckperms.api.query.QueryOptions; +import net.minecraft.server.level.ServerPlayer; + +import java.util.UUID; + +public class NeoForgeContextManager extends ContextManager { + public static final OptionKey INTEGRATED_SERVER_OWNER = OptionKey.of("integrated_server_owner", Boolean.class); + + public NeoForgeContextManager(LPNeoForgePlugin plugin) { + super(plugin, ServerPlayer.class, ServerPlayer.class); + } + + @Override + public UUID getUniqueId(ServerPlayer player) { + return player.getUUID(); + } + + @Override + public QueryOptionsCache 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(); + } + } + +} diff --git a/neoforge/src/main/java/me/lucko/luckperms/neoforge/context/NeoForgePlayerCalculator.java b/neoforge/src/main/java/me/lucko/luckperms/neoforge/context/NeoForgePlayerCalculator.java new file mode 100644 index 000000000..c8595e605 --- /dev/null +++ b/neoforge/src/main/java/me/lucko/luckperms/neoforge/context/NeoForgePlayerCalculator.java @@ -0,0 +1,147 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge.context; + +import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.common.context.ImmutableContextSetImpl; +import me.lucko.luckperms.neoforge.LPNeoForgePlugin; +import net.luckperms.api.context.Context; +import net.luckperms.api.context.ContextCalculator; +import net.luckperms.api.context.ContextConsumer; +import net.luckperms.api.context.ContextSet; +import net.luckperms.api.context.DefaultContextKeys; +import net.luckperms.api.context.ImmutableContextSet; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.storage.ServerLevelData; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Set; + +public class NeoForgePlayerCalculator implements ContextCalculator { + /** + * GameType.NOT_SET(-1, "") was removed in 1.17 + */ + private static final int GAME_MODE_NOT_SET = -1; + + private final LPNeoForgePlugin plugin; + + private final boolean gamemode; + private final boolean world; + private final boolean dimensionType; + + public NeoForgePlayerCalculator(LPNeoForgePlugin plugin, Set disabled) { + this.plugin = plugin; + this.gamemode = !disabled.contains(DefaultContextKeys.GAMEMODE_KEY); + this.world = !disabled.contains(DefaultContextKeys.WORLD_KEY); + this.dimensionType = !disabled.contains(DefaultContextKeys.DIMENSION_TYPE_KEY); + } + + @Override + public void calculate(@NonNull ServerPlayer target, @NonNull ContextConsumer consumer) { + ServerLevel level = target.serverLevel(); + if (this.dimensionType) { + consumer.accept(DefaultContextKeys.DIMENSION_TYPE_KEY, getContextKey(level.dimension().location())); + } + + ServerLevelData levelData = (ServerLevelData) level.getLevelData(); + if (this.world) { + this.plugin.getConfiguration().get(ConfigKeys.WORLD_REWRITES).rewriteAndSubmit(levelData.getLevelName(), consumer); + } + + GameType gameMode = target.gameMode.getGameModeForPlayer(); + if (this.gamemode && gameMode.getId() != GAME_MODE_NOT_SET) { + consumer.accept(DefaultContextKeys.GAMEMODE_KEY, gameMode.getName()); + } + } + + @Override + public @NonNull ContextSet estimatePotentialContexts() { + ImmutableContextSet.Builder builder = new ImmutableContextSetImpl.BuilderImpl(); + + if (this.gamemode) { + for (GameType gameType : GameType.values()) { + if (gameType.getId() == GAME_MODE_NOT_SET) { + continue; + } + + builder.add(DefaultContextKeys.GAMEMODE_KEY, gameType.getName()); + } + } + + MinecraftServer server = this.plugin.getBootstrap().getServer().orElse(null); + if (this.dimensionType && server != null) { + server.registryAccess().registry(Registries.DIMENSION_TYPE).ifPresent(registry -> { + for (ResourceLocation resourceLocation : registry.keySet()) { + builder.add(DefaultContextKeys.DIMENSION_TYPE_KEY, getContextKey(resourceLocation)); + } + }); + } + + if (this.world && server != null) { + for (ServerLevel level : server.getAllLevels()) { + ServerLevelData levelData = (ServerLevelData) level.getLevelData(); + if (Context.isValidValue(levelData.getLevelName())) { + builder.add(DefaultContextKeys.WORLD_KEY, levelData.getLevelName()); + } + } + } + + return builder.build(); + } + + private static String getContextKey(ResourceLocation key) { + if (key.getNamespace().equals("minecraft")) { + return key.getPath(); + } + return key.toString(); + } + + @SubscribeEvent + public void onPlayerChangedDimension(PlayerEvent.PlayerChangedDimensionEvent event) { + if (!(this.world || this.dimensionType)) { + return; + } + + this.plugin.getContextManager().signalContextUpdate((ServerPlayer) event.getEntity()); + } + + @SubscribeEvent + public void onPlayerChangeGameMode(PlayerEvent.PlayerChangeGameModeEvent event) { + if (!this.gamemode || event.getNewGameMode().getId() == GAME_MODE_NOT_SET) { + return; + } + + this.plugin.getContextManager().signalContextUpdate((ServerPlayer) event.getEntity()); + } + +} diff --git a/neoforge/src/main/java/me/lucko/luckperms/neoforge/listeners/NeoForgeAutoOpListener.java b/neoforge/src/main/java/me/lucko/luckperms/neoforge/listeners/NeoForgeAutoOpListener.java new file mode 100644 index 000000000..d40031f61 --- /dev/null +++ b/neoforge/src/main/java/me/lucko/luckperms/neoforge/listeners/NeoForgeAutoOpListener.java @@ -0,0 +1,97 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge.listeners; + +import me.lucko.luckperms.common.api.implementation.ApiUser; +import me.lucko.luckperms.common.event.LuckPermsEventListener; +import me.lucko.luckperms.common.model.User; +import me.lucko.luckperms.neoforge.LPNeoForgePlugin; +import net.luckperms.api.event.EventBus; +import net.luckperms.api.event.context.ContextUpdateEvent; +import net.luckperms.api.event.user.UserDataRecalculateEvent; +import net.luckperms.api.query.QueryOptions; +import net.minecraft.server.level.ServerPlayer; + +import java.util.Map; + +public class NeoForgeAutoOpListener implements LuckPermsEventListener { + private static final String NODE = "luckperms.autoop"; + + private final LPNeoForgePlugin plugin; + + public NeoForgeAutoOpListener(LPNeoForgePlugin plugin) { + this.plugin = plugin; + } + + @Override + public void bind(EventBus bus) { + bus.subscribe(ContextUpdateEvent.class, this::onContextUpdate); + bus.subscribe(UserDataRecalculateEvent.class, this::onUserDataRecalculate); + } + + private void onContextUpdate(ContextUpdateEvent event) { + event.getSubject(ServerPlayer.class).ifPresent(player -> refreshAutoOp(player, true)); + } + + private void onUserDataRecalculate(UserDataRecalculateEvent event) { + User user = ApiUser.cast(event.getUser()); + this.plugin.getBootstrap().getPlayer(user.getUniqueId()).ifPresent(player -> refreshAutoOp(player, false)); + } + + private void refreshAutoOp(ServerPlayer player, boolean callerIsSync) { + if (!callerIsSync && !this.plugin.getBootstrap().getServer().isPresent()) { + return; + } + + User user = this.plugin.getUserManager().getIfLoaded(player.getUUID()); + + boolean value; + if (user != null) { + QueryOptions queryOptions = this.plugin.getContextManager().getQueryOptions(player); + Map 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()); + } + }); + } + +} diff --git a/neoforge/src/main/java/me/lucko/luckperms/neoforge/listeners/NeoForgeCommandListUpdater.java b/neoforge/src/main/java/me/lucko/luckperms/neoforge/listeners/NeoForgeCommandListUpdater.java new file mode 100644 index 000000000..8de108643 --- /dev/null +++ b/neoforge/src/main/java/me/lucko/luckperms/neoforge/listeners/NeoForgeCommandListUpdater.java @@ -0,0 +1,119 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge.listeners; + +import com.github.benmanes.caffeine.cache.LoadingCache; +import me.lucko.luckperms.common.api.implementation.ApiGroup; +import me.lucko.luckperms.common.cache.BufferedRequest; +import me.lucko.luckperms.common.event.LuckPermsEventListener; +import me.lucko.luckperms.common.util.CaffeineFactory; +import me.lucko.luckperms.neoforge.LPNeoForgePlugin; +import net.luckperms.api.event.EventBus; +import net.luckperms.api.event.context.ContextUpdateEvent; +import net.luckperms.api.event.group.GroupDataRecalculateEvent; +import net.luckperms.api.event.user.UserDataRecalculateEvent; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + * Calls {@link net.minecraft.server.players.PlayerList#sendPlayerPermissionLevel(ServerPlayer)} when a players permissions change. + */ +public class NeoForgeCommandListUpdater implements LuckPermsEventListener { + private final LPNeoForgePlugin plugin; + private final LoadingCache sendingBuffers = CaffeineFactory.newBuilder() + .expireAfterAccess(10, TimeUnit.SECONDS) + .build(SendBuffer::new); + + public NeoForgeCommandListUpdater(LPNeoForgePlugin plugin) { + this.plugin = plugin; + } + + @Override + public void bind(EventBus bus) { + bus.subscribe(UserDataRecalculateEvent.class, this::onUserDataRecalculate); + bus.subscribe(GroupDataRecalculateEvent.class, this::onGroupDataRecalculate); + bus.subscribe(ContextUpdateEvent.class, this::onContextUpdate); + } + + private void onUserDataRecalculate(UserDataRecalculateEvent e) { + requestUpdate(e.getUser().getUniqueId()); + } + + private void onGroupDataRecalculate(GroupDataRecalculateEvent e) { + plugin.getUserManager().getAll().values().forEach(user -> { + if (user.resolveInheritanceTree(user.getQueryOptions()).contains(ApiGroup.cast(e.getGroup()))) { + requestUpdate(user.getUniqueId()); + } + }); + } + + private void onContextUpdate(ContextUpdateEvent e) { + e.getSubject(ServerPlayer.class).ifPresent(p -> requestUpdate(p.getUUID())); + } + + private void requestUpdate(UUID uniqueId) { + if (!this.plugin.getBootstrap().isPlayerOnline(uniqueId)) { + return; + } + + // Buffer the request to send a commands update. + SendBuffer sendBuffer = this.sendingBuffers.get(uniqueId); + if (sendBuffer != null) { + sendBuffer.request(); + } + } + + // Called when the buffer times out. + private void sendUpdate(UUID uniqueId) { + this.plugin.getBootstrap().getScheduler().sync().execute(() -> { + this.plugin.getBootstrap().getPlayer(uniqueId).ifPresent(player -> { + MinecraftServer server = player.getServer(); + if (server != null) { + server.getPlayerList().sendPlayerPermissionLevel(player); + } + }); + }); + } + + private final class SendBuffer extends BufferedRequest { + private final UUID uniqueId; + + SendBuffer(UUID uniqueId) { + super(500, TimeUnit.MILLISECONDS, NeoForgeCommandListUpdater.this.plugin.getBootstrap().getScheduler()); + this.uniqueId = uniqueId; + } + + @Override + protected Void perform() { + sendUpdate(this.uniqueId); + return null; + } + } + +} diff --git a/neoforge/src/main/java/me/lucko/luckperms/neoforge/listeners/NeoForgeConnectionListener.java b/neoforge/src/main/java/me/lucko/luckperms/neoforge/listeners/NeoForgeConnectionListener.java new file mode 100644 index 000000000..c4a91e165 --- /dev/null +++ b/neoforge/src/main/java/me/lucko/luckperms/neoforge/listeners/NeoForgeConnectionListener.java @@ -0,0 +1,158 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge.listeners; + +import com.mojang.authlib.GameProfile; +import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.common.locale.Message; +import me.lucko.luckperms.common.locale.TranslationManager; +import me.lucko.luckperms.common.model.User; +import me.lucko.luckperms.common.plugin.util.AbstractConnectionListener; +import me.lucko.luckperms.neoforge.NeoForgeSenderFactory; +import me.lucko.luckperms.neoforge.LPNeoForgePlugin; +import me.lucko.luckperms.neoforge.capabilities.UserCapabilityImpl; +import me.lucko.luckperms.neoforge.util.AsyncConfigurationTask; +import net.kyori.adventure.text.Component; +import net.minecraft.network.Connection; +import net.minecraft.network.PacketListener; +import net.minecraft.network.protocol.login.ClientboundLoginDisconnectPacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ConfigurationTask; +import net.minecraft.server.network.ServerConfigurationPacketListenerImpl; + +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import net.neoforged.bus.api.EventPriority; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent; +import net.neoforged.neoforge.network.event.RegisterConfigurationTasksEvent; + +public class NeoForgeConnectionListener extends AbstractConnectionListener { + private static final ConfigurationTask.Type USER_LOGIN_TASK_TYPE = new ConfigurationTask.Type("luckperms:user_login"); + + private final LPNeoForgePlugin plugin; + + public NeoForgeConnectionListener(LPNeoForgePlugin plugin) { + super(plugin); + this.plugin = plugin; + } + + @SubscribeEvent + public void onGatherLoginConfigurationTasks(RegisterConfigurationTasksEvent event) { + PacketListener packetListener = event.getListener(); + if (!(packetListener instanceof ServerConfigurationPacketListenerImpl)) { + return; + } + + GameProfile gameProfile = ((ServerConfigurationPacketListenerImpl) packetListener).getOwner(); + if (gameProfile == null) { + return; + } + + String username = gameProfile.getName(); + UUID uniqueId = gameProfile.getId(); + + if (this.plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) { + this.plugin.getLogger().info("Processing pre-login (sync phase) for " + uniqueId + " - " + username); + } + + event.register(new AsyncConfigurationTask(this.plugin, USER_LOGIN_TASK_TYPE, () -> CompletableFuture.runAsync(() -> { + onPlayerNegotiationAsync(event.getListener().getConnection(), uniqueId, username); + }, this.plugin.getBootstrap().getScheduler().async()))); + } + + private void onPlayerNegotiationAsync(Connection connection, UUID uniqueId, String username) { + if (this.plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) { + this.plugin.getLogger().info("Processing pre-login (async phase) for " + uniqueId + " - " + username); + } + + /* Actually process the login for the connection. + We do this here to delay the login until the data is ready. + If the login gets cancelled later on, then this will be cleaned up. + + This includes: + - loading uuid data + - loading permissions + - creating a user instance in the UserManager for this connection. + - setting up cached data. */ + try { + User user = loadUser(uniqueId, username); + recordConnection(uniqueId); + this.plugin.getEventDispatcher().dispatchPlayerLoginProcess(uniqueId, username, user); + } catch (Exception ex) { + this.plugin.getLogger().severe("Exception occurred whilst loading data for " + uniqueId + " - " + username, ex); + + if (this.plugin.getConfiguration().get(ConfigKeys.CANCEL_FAILED_LOGINS)) { + Component component = TranslationManager.render(Message.LOADING_DATABASE_ERROR.build()); + connection.send(new ClientboundLoginDisconnectPacket(NeoForgeSenderFactory.toNativeText(component))); + connection.disconnect(NeoForgeSenderFactory.toNativeText(component)); + this.plugin.getEventDispatcher().dispatchPlayerLoginProcess(uniqueId, username, null); + } + } + } + + @SubscribeEvent(priority = EventPriority.HIGHEST) + public void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) { + ServerPlayer player = (ServerPlayer) event.getEntity(); + GameProfile profile = player.getGameProfile(); + + if (this.plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) { + this.plugin.getLogger().info("Processing post-login for " + profile.getId() + " - " + profile.getName()); + } + + User user = this.plugin.getUserManager().getIfLoaded(profile.getId()); + + if (user == null) { + if (!getUniqueConnections().contains(profile.getId())) { + this.plugin.getLogger().warn("User " + profile.getId() + " - " + profile.getName() + + " doesn't have data pre-loaded, they have never been processed during pre-login in this session."); + } else { + this.plugin.getLogger().warn("User " + profile.getId() + " - " + profile.getName() + + " doesn't currently have data pre-loaded, but they have been processed before in this session."); + } + + Component component = TranslationManager.render(Message.LOADING_STATE_ERROR.build(), player.getLanguage()); + if (this.plugin.getConfiguration().get(ConfigKeys.CANCEL_FAILED_LOGINS)) { + player.connection.disconnect(NeoForgeSenderFactory.toNativeText(component)); + return; + } else { + player.sendSystemMessage(NeoForgeSenderFactory.toNativeText(component)); + } + } + + // initialise capability + UserCapabilityImpl userCapability = UserCapabilityImpl.get(player); + userCapability.initialise(user, player, this.plugin.getContextManager()); + this.plugin.getContextManager().signalContextUpdate(player); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void onPlayerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) { + ServerPlayer player = (ServerPlayer) event.getEntity(); + handleDisconnect(player.getGameProfile().getId()); + } + +} \ No newline at end of file diff --git a/neoforge/src/main/java/me/lucko/luckperms/neoforge/listeners/NeoForgePlatformListener.java b/neoforge/src/main/java/me/lucko/luckperms/neoforge/listeners/NeoForgePlatformListener.java new file mode 100644 index 000000000..1d04d8ca1 --- /dev/null +++ b/neoforge/src/main/java/me/lucko/luckperms/neoforge/listeners/NeoForgePlatformListener.java @@ -0,0 +1,92 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge.listeners; + +import com.mojang.brigadier.context.CommandContextBuilder; +import com.mojang.brigadier.context.ParsedCommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; +import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.common.locale.Message; +import me.lucko.luckperms.neoforge.LPNeoForgePlugin; +import me.lucko.luckperms.neoforge.util.BrigadierInjector; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.server.players.ServerOpList; + +import java.io.IOException; +import java.util.Locale; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.event.AddReloadListenerEvent; +import net.neoforged.neoforge.event.CommandEvent; +import net.neoforged.neoforge.event.server.ServerStartedEvent; + +public class NeoForgePlatformListener { + private final LPNeoForgePlugin plugin; + + public NeoForgePlatformListener(LPNeoForgePlugin plugin) { + this.plugin = plugin; + } + + @SubscribeEvent + public void onCommand(CommandEvent event) { + CommandContextBuilder context = event.getParseResults().getContext(); + + if (!this.plugin.getConfiguration().get(ConfigKeys.OPS_ENABLED)) { + for (ParsedCommandNode 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); + } + } + } + +} diff --git a/neoforge/src/main/java/me/lucko/luckperms/neoforge/messaging/NeoForgeMessagingFactory.java b/neoforge/src/main/java/me/lucko/luckperms/neoforge/messaging/NeoForgeMessagingFactory.java new file mode 100644 index 000000000..26f35e71d --- /dev/null +++ b/neoforge/src/main/java/me/lucko/luckperms/neoforge/messaging/NeoForgeMessagingFactory.java @@ -0,0 +1,70 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge.messaging; + +import me.lucko.luckperms.common.messaging.InternalMessagingService; +import me.lucko.luckperms.common.messaging.LuckPermsMessagingService; +import me.lucko.luckperms.common.messaging.MessagingFactory; +import me.lucko.luckperms.neoforge.LPNeoForgePlugin; +import net.luckperms.api.messenger.IncomingMessageConsumer; +import net.luckperms.api.messenger.Messenger; +import net.luckperms.api.messenger.MessengerProvider; +import org.checkerframework.checker.nullness.qual.NonNull; + +public class NeoForgeMessagingFactory extends MessagingFactory { + public NeoForgeMessagingFactory(LPNeoForgePlugin plugin) { + super(plugin); + } + + @Override + protected InternalMessagingService getServiceFor(String messagingType) { + if (messagingType.equals("pluginmsg") || messagingType.equals("bungee") || messagingType.equals("velocity")) { + try { + return new LuckPermsMessagingService(getPlugin(), new PluginMessageMessengerProvider()); + } catch (Exception e) { + getPlugin().getLogger().severe("Exception occurred whilst enabling messaging", e); + } + } + + return super.getServiceFor(messagingType); + } + + private class PluginMessageMessengerProvider implements MessengerProvider { + + @Override + public @NonNull String getName() { + return "PluginMessage"; + } + + @Override + public @NonNull Messenger obtain(@NonNull IncomingMessageConsumer incomingMessageConsumer) { + PluginMessageMessenger messenger = new PluginMessageMessenger(getPlugin(), incomingMessageConsumer); + messenger.init(); + return messenger; + } + } + +} diff --git a/neoforge/src/main/java/me/lucko/luckperms/neoforge/messaging/PluginMessageMessenger.java b/neoforge/src/main/java/me/lucko/luckperms/neoforge/messaging/PluginMessageMessenger.java new file mode 100644 index 000000000..6bfbcbd69 --- /dev/null +++ b/neoforge/src/main/java/me/lucko/luckperms/neoforge/messaging/PluginMessageMessenger.java @@ -0,0 +1,114 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge.messaging; + +import com.google.common.collect.Iterables; +import me.lucko.luckperms.common.messaging.pluginmsg.AbstractPluginMessageMessenger; +import me.lucko.luckperms.common.plugin.scheduler.SchedulerTask; +import me.lucko.luckperms.neoforge.LPNeoForgePlugin; +import net.luckperms.api.messenger.IncomingMessageConsumer; +import net.luckperms.api.messenger.Messenger; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.PlayerList; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.network.PacketDistributor; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; +import net.neoforged.neoforge.network.registration.HandlerThread; + +public class PluginMessageMessenger extends AbstractPluginMessageMessenger implements Messenger { + private static final ResourceLocation CHANNEL_ID = ResourceLocation.parse(AbstractPluginMessageMessenger.CHANNEL); + private static final CustomPacketPayload.Type PAYLOAD_TYPE = new CustomPacketPayload.Type<>(CHANNEL_ID); + + private final LPNeoForgePlugin plugin; + + public PluginMessageMessenger(LPNeoForgePlugin plugin, IncomingMessageConsumer consumer) { + super(consumer); + this.plugin = plugin; + } + + @SubscribeEvent + private void register(final RegisterPayloadHandlersEvent event) { + event.registrar("1").executesOn(HandlerThread.NETWORK).commonBidirectional( + PAYLOAD_TYPE, + StreamCodec.of( + (bytebuf, wrapper) -> bytebuf.writeBytes(wrapper.bytes), + buf -> { + byte[] bytes = new byte[buf.readableBytes()]; + return new MessageWrapper(bytes); + } + ), + (payload, context) -> handleIncomingMessage(payload.bytes()) + ); + } + + public void init() { + this.plugin.getBootstrap().registerListeners(this); + } + + @Override + protected void sendOutgoingMessage(byte[] buf) { + AtomicReference taskRef = new AtomicReference<>(); + SchedulerTask task = this.plugin.getBootstrap().getScheduler().asyncRepeating(() -> { + ServerPlayer player = this.plugin.getBootstrap().getServer() + .map(MinecraftServer::getPlayerList) + .map(PlayerList::getPlayers) + .map(players -> Iterables.getFirst(players, null)) + .orElse(null); + + if (player == null) { + return; + } + + PacketDistributor.sendToPlayer(player, new MessageWrapper(buf)); + + SchedulerTask t = taskRef.getAndSet(null); + if (t != null) { + t.cancel(); + } + }, 10, TimeUnit.SECONDS); + taskRef.set(task); + } + + public static void registerChannel() { + // do nothing - the channels are registered in the static initializer, we just + // need to make sure that is called (which it will be if this method runs) + } + + public record MessageWrapper(byte[] bytes) implements CustomPacketPayload { + @Override + public Type type() { + return PAYLOAD_TYPE; + } + } + +} diff --git a/neoforge/src/main/java/me/lucko/luckperms/neoforge/service/NeoForgePermissionHandler.java b/neoforge/src/main/java/me/lucko/luckperms/neoforge/service/NeoForgePermissionHandler.java new file mode 100644 index 000000000..85966a01c --- /dev/null +++ b/neoforge/src/main/java/me/lucko/luckperms/neoforge/service/NeoForgePermissionHandler.java @@ -0,0 +1,166 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge.service; + +import me.lucko.luckperms.common.cacheddata.type.MetaCache; +import me.lucko.luckperms.common.cacheddata.type.PermissionCache; +import me.lucko.luckperms.common.context.ImmutableContextSetImpl; +import me.lucko.luckperms.common.model.User; +import me.lucko.luckperms.common.verbose.event.CheckOrigin; +import me.lucko.luckperms.neoforge.LPNeoForgeBootstrap; +import me.lucko.luckperms.neoforge.LPNeoForgePlugin; +import me.lucko.luckperms.neoforge.capabilities.UserCapabilityImpl; +import net.luckperms.api.context.ImmutableContextSet; +import net.luckperms.api.query.QueryMode; +import net.luckperms.api.query.QueryOptions; +import net.luckperms.api.util.Tristate; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import net.neoforged.neoforge.server.permission.handler.IPermissionHandler; +import net.neoforged.neoforge.server.permission.nodes.PermissionDynamicContext; +import net.neoforged.neoforge.server.permission.nodes.PermissionNode; +import net.neoforged.neoforge.server.permission.nodes.PermissionType; +import net.neoforged.neoforge.server.permission.nodes.PermissionTypes; + +public class NeoForgePermissionHandler implements IPermissionHandler { + public static final ResourceLocation IDENTIFIER = ResourceLocation.fromNamespaceAndPath(LPNeoForgeBootstrap.ID, "permission_handler"); + + private final LPNeoForgePlugin plugin; + private final Set> permissionNodes; + + public NeoForgePermissionHandler(LPNeoForgePlugin plugin, Collection> 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> getRegisteredNodes() { + return this.permissionNodes; + } + + @Override + public T getPermission(ServerPlayer player, PermissionNode 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 getOfflinePermission(UUID player, PermissionNode 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 getPermissionValue(User user, QueryOptions queryOptions, PermissionNode node, PermissionDynamicContext... context) { + queryOptions = appendContextToQueryOptions(queryOptions, context); + String key = node.getNodeName(); + PermissionType type = node.getType(); + + // permission check + if (type == PermissionTypes.BOOLEAN) { + PermissionCache cache = user.getCachedData().getPermissionData(queryOptions); + Tristate value = cache.checkPermission(key, CheckOrigin.PLATFORM_API_HAS_PERMISSION).result(); + if (value != Tristate.UNDEFINED) { + return (T) (Boolean) value.asBoolean(); + } + } + + // meta lookup + if (node.getType() == PermissionTypes.STRING) { + MetaCache cache = user.getCachedData().getMetaData(queryOptions); + String value = cache.getMetaOrChatMetaValue(node.getNodeName(), CheckOrigin.PLATFORM_API); + if (value != null) { + return (T) value; + } + } + + // meta lookup (integer) + if (node.getType() == PermissionTypes.INTEGER) { + MetaCache cache = user.getCachedData().getMetaData(queryOptions); + String value = cache.getMetaOrChatMetaValue(node.getNodeName(), CheckOrigin.PLATFORM_API); + if (value != null) { + try { + return (T) Integer.valueOf(Integer.parseInt(value)); + } catch (IllegalArgumentException e) { + // ignore + } + } + } + + return null; + } + + private static QueryOptions appendContextToQueryOptions(QueryOptions queryOptions, PermissionDynamicContext... context) { + if (context.length == 0 || queryOptions.mode() != QueryMode.CONTEXTUAL) { + return queryOptions; + } + + ImmutableContextSet.Builder contextBuilder = new ImmutableContextSetImpl.BuilderImpl() + .addAll(queryOptions.context()); + + for (PermissionDynamicContext dynamicContext : context) { + contextBuilder.add(dynamicContext.getDynamic().name(), dynamicContext.getSerializedValue()); + } + + return queryOptions.toBuilder().context(contextBuilder.build()).build(); + } + +} diff --git a/neoforge/src/main/java/me/lucko/luckperms/neoforge/service/NeoForgePermissionHandlerListener.java b/neoforge/src/main/java/me/lucko/luckperms/neoforge/service/NeoForgePermissionHandlerListener.java new file mode 100644 index 000000000..96bbe13e4 --- /dev/null +++ b/neoforge/src/main/java/me/lucko/luckperms/neoforge/service/NeoForgePermissionHandlerListener.java @@ -0,0 +1,64 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge.service; + +import me.lucko.luckperms.common.command.access.CommandPermission; +import me.lucko.luckperms.neoforge.LPNeoForgePlugin; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.common.ModConfigSpec; +import net.neoforged.neoforge.common.NeoForgeConfig; +import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent; +import net.neoforged.neoforge.server.permission.handler.DefaultPermissionHandler; +import net.neoforged.neoforge.server.permission.nodes.PermissionNode; +import net.neoforged.neoforge.server.permission.nodes.PermissionTypes; + +public class NeoForgePermissionHandlerListener { + private final LPNeoForgePlugin plugin; + + public NeoForgePermissionHandlerListener(LPNeoForgePlugin plugin) { + this.plugin = plugin; + } + + @SubscribeEvent + public void onPermissionGatherHandler(PermissionGatherEvent.Handler event) { + // Override the default permission handler with LuckPerms + ModConfigSpec.ConfigValue permissionHandler = NeoForgeConfig.SERVER.permissionHandler; + if (permissionHandler.get().equals(DefaultPermissionHandler.IDENTIFIER.toString())) { + permissionHandler.set(NeoForgePermissionHandler.IDENTIFIER.toString()); + } + + event.addPermissionHandler(NeoForgePermissionHandler.IDENTIFIER, permissions -> new NeoForgePermissionHandler(this.plugin, permissions)); + } + + @SubscribeEvent + public void onPermissionGatherNodes(PermissionGatherEvent.Nodes event) { + // register luckperms nodes + for (CommandPermission permission : CommandPermission.values()) { + event.addNodes(new PermissionNode<>("luckperms", permission.getNode(), PermissionTypes.BOOLEAN, (player, uuid, context) -> false)); + } + } + +} diff --git a/neoforge/src/main/java/me/lucko/luckperms/neoforge/util/AsyncConfigurationTask.java b/neoforge/src/main/java/me/lucko/luckperms/neoforge/util/AsyncConfigurationTask.java new file mode 100644 index 000000000..748ba9fe2 --- /dev/null +++ b/neoforge/src/main/java/me/lucko/luckperms/neoforge/util/AsyncConfigurationTask.java @@ -0,0 +1,61 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge.util; + +import me.lucko.luckperms.neoforge.LPNeoForgePlugin; +import net.minecraft.network.protocol.Packet; +import net.minecraft.server.network.ConfigurationTask; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class AsyncConfigurationTask implements ConfigurationTask { + private final LPNeoForgePlugin plugin; + private final Type type; + private final Supplier> task; + + public AsyncConfigurationTask(LPNeoForgePlugin plugin, Type type, Supplier> task) { + this.plugin = plugin; + this.type = type; + this.task = task; + } + + @Override + public void start(Consumer> send) { + CompletableFuture future = this.task.get(); + future.whenCompleteAsync((o, e) -> { + if (e != null) { + this.plugin.getLogger().warn("Configuration task threw an exception", e); + } + }).join(); + } + + @Override + public Type type() { + return this.type; + } +} \ No newline at end of file diff --git a/neoforge/src/main/java/me/lucko/luckperms/neoforge/util/BrigadierInjector.java b/neoforge/src/main/java/me/lucko/luckperms/neoforge/util/BrigadierInjector.java new file mode 100644 index 000000000..7cd65d6df --- /dev/null +++ b/neoforge/src/main/java/me/lucko/luckperms/neoforge/util/BrigadierInjector.java @@ -0,0 +1,197 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge.util; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.tree.CommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; +import me.lucko.luckperms.common.graph.Graph; +import me.lucko.luckperms.common.graph.TraversalAlgorithm; +import me.lucko.luckperms.common.model.User; +import me.lucko.luckperms.neoforge.LPNeoForgePlugin; +import me.lucko.luckperms.neoforge.capabilities.UserCapability; +import me.lucko.luckperms.neoforge.capabilities.UserCapabilityImpl; +import net.luckperms.api.util.Tristate; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.server.level.ServerPlayer; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Locale; +import java.util.function.Predicate; + +/** + * Utility for injecting permission requirements into a Brigadier command tree. + */ +public final class BrigadierInjector { + private BrigadierInjector() {} + + private static final Field REQUIREMENT_FIELD; + + static { + Field requirementField; + try { + requirementField = CommandNode.class.getDeclaredField("requirement"); + requirementField.setAccessible(true); + } catch (NoSuchFieldException e) { + throw new ExceptionInInitializerError(e); + } + REQUIREMENT_FIELD = requirementField; + } + + /** + * Inject permission requirements into the commands in the given dispatcher. + * + * @param plugin the plugin + * @param dispatcher the command dispatcher + */ + public static void inject(LPNeoForgePlugin plugin, CommandDispatcher dispatcher) { + Iterable tree = CommandNodeGraph.INSTANCE.traverse( + TraversalAlgorithm.DEPTH_FIRST_PRE_ORDER, + new CommandNodeWithParent(null, dispatcher.getRoot()) + ); + + for (CommandNodeWithParent node : tree) { + Predicate requirement = node.node.getRequirement(); + + // already injected - skip + if (requirement instanceof InjectedPermissionRequirement) { + continue; + } + + String permission = buildPermissionNode(node); + if (permission == null) { + continue; + } + + plugin.getPermissionRegistry().insert(permission); + + InjectedPermissionRequirement newRequirement = new InjectedPermissionRequirement(plugin, permission, requirement); + try { + REQUIREMENT_FIELD.set(node.node, newRequirement); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + } + + private static String buildPermissionNode(CommandNodeWithParent node) { + StringBuilder builder = new StringBuilder(); + + while (node != null) { + if (node.node instanceof LiteralCommandNode) { + if (builder.length() != 0) { + builder.insert(0, '.'); + } + + String name = node.node.getName().toLowerCase(Locale.ROOT); + builder.insert(0, name); + } + + node = node.parent; + } + + if (builder.length() == 0) { + return null; + } + + builder.insert(0, "command."); + return builder.toString(); + } + + /** + * Injected {@link CommandNode#getRequirement() requirement} that checks for a permission, before + * delegating to the existing requirement. + */ + private static final class InjectedPermissionRequirement implements Predicate { + private final LPNeoForgePlugin plugin; + private final String permission; + private final Predicate delegate; + + private InjectedPermissionRequirement(LPNeoForgePlugin plugin, String permission, Predicate delegate) { + this.plugin = plugin; + this.permission = permission; + this.delegate = delegate; + } + + @Override + public boolean test(CommandSourceStack source) { + if (source.getEntity() instanceof ServerPlayer) { + ServerPlayer player = (ServerPlayer) source.getEntity(); + Tristate state = Tristate.UNDEFINED; + // If player is still connecting and has not been added to world then check LP user directly + if (!player.isAddedToLevel()) { + User user = this.plugin.getUserManager().getIfLoaded(player.getUUID()); + if (user == null) { + // Should never happen but just in case... + return false; + } + state = user.getCachedData().getPermissionData().checkPermission(permission); + } else { + UserCapability user = UserCapabilityImpl.get(player); + state = user.checkPermission(this.permission); + } + + if (state != Tristate.UNDEFINED) { + return state.asBoolean() && this.delegate.test(source.withPermission(4)); + } + } + + return this.delegate.test(source); + } + } + + /** + * A {@link Graph} to represent the brigadier command node tree. + */ + private enum CommandNodeGraph implements Graph { + INSTANCE; + + @Override + public Iterable successors(CommandNodeWithParent ctx) { + CommandNode node = ctx.node; + Collection successors = new ArrayList<>(); + + for (CommandNode child : node.getChildren()) { + successors.add(new CommandNodeWithParent(ctx, child)); + } + + return successors; + } + } + + private static final class CommandNodeWithParent { + private final CommandNodeWithParent parent; + private final CommandNode node; + + private CommandNodeWithParent(CommandNodeWithParent parent, CommandNode node) { + this.parent = parent; + this.node = node; + } + } + +} diff --git a/neoforge/src/main/java/me/lucko/luckperms/neoforge/util/NeoForgeEventBusFacade.java b/neoforge/src/main/java/me/lucko/luckperms/neoforge/util/NeoForgeEventBusFacade.java new file mode 100644 index 000000000..e2cdc129f --- /dev/null +++ b/neoforge/src/main/java/me/lucko/luckperms/neoforge/util/NeoForgeEventBusFacade.java @@ -0,0 +1,241 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.neoforge.util; + +import me.lucko.luckperms.common.loader.JarInJarClassLoader; +import net.neoforged.bus.api.Event; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.ModLoadingContext; +import net.neoforged.fml.event.IModBusEvent; + +import java.lang.invoke.CallSite; +import java.lang.invoke.LambdaMetafactory; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import net.neoforged.neoforge.common.NeoForge; + +/** + * A utility for registering Forge listeners for methods in a jar-in-jar. + * + *

This differs from {@link IEventBus#register} as reflection is used for invoking the registered listeners + * instead of ASM, which is incompatible with {@link JarInJarClassLoader}

+ */ +public class NeoForgeEventBusFacade { + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + private final List listeners = new ArrayList<>(); + + /** + * Register listeners for all methods annotated with {@link SubscribeEvent} on the target object. + */ + public void register(Object target) { + for (Method method : target.getClass().getMethods()) { + // Ignore static methods, Support for these could be added, but they are not used in LuckPerms + if (Modifier.isStatic(method.getModifiers())) { + continue; + } + + // Methods require a SubscribeEvent annotation in order to be registered + SubscribeEvent subscribeEvent = method.getAnnotation(SubscribeEvent.class); + if (subscribeEvent == null) { + continue; + } + + EventType type = determineListenerType(method); + Consumer invoker = createInvokerFunction(method, target, type); + + // Determine the 'IEventBus' that this eventType should be registered to. + IEventBus eventBus; + if (IModBusEvent.class.isAssignableFrom(type.eventType)) { + eventBus = ModLoadingContext.get().getActiveContainer().getEventBus(); + } else { + eventBus = NeoForge.EVENT_BUS; + } + + addListener(eventBus, subscribeEvent, type.eventType, invoker); + + this.listeners.add(new ListenerRegistration(invoker, eventBus, target)); + } + } + + /** + * Unregister previously registered listeners on the target object. + * + * @param target the target listener + */ + public void unregister(Object target) { + this.listeners.removeIf(listener -> { + if (listener.target == target) { + listener.close(); + return true; + } else { + return false; + } + }); + } + + /** + * Unregister all listeners created through this interface. + */ + public void unregisterAll() { + for (ListenerRegistration listener : this.listeners) { + listener.close(); + } + this.listeners.clear(); + } + + /** + * A listener registration. + */ + private static final class ListenerRegistration implements AutoCloseable { + /** The lambda invoker function */ + private final Consumer invoker; + /** The event bus that the invoker was registered to */ + private final IEventBus eventBus; + /** The target listener class */ + private final Object target; + + private ListenerRegistration(Consumer invoker, IEventBus eventBus, Object target) { + this.invoker = invoker; + this.eventBus = eventBus; + this.target = target; + } + + @Override + public void close() { + this.eventBus.unregister(this.invoker); + } + } + + private static Consumer createInvokerFunction(Method method, Object target, EventType type) { + // Use the 'LambdaMetafactory' to generate a consumer which can be passed directly to an 'IEventBus' + // when registering a listener, this reduces the overhead involved when reflectively invoking methods. + try { + MethodHandle methodHandle = LOOKUP.unreflect(method); + CallSite callSite = LambdaMetafactory.metafactory( + LOOKUP, + "accept", + MethodType.methodType(Consumer.class, target.getClass()), + MethodType.methodType(void.class, Object.class), + methodHandle, + MethodType.methodType(void.class, type.eventType) + ); + + return (Consumer) callSite.getTarget().bindTo(target).invokeExact(); + } catch (Throwable t) { + throw new RuntimeException("Error whilst registering " + method, t); + } + } + + public static EventType determineListenerType(Method method) { + // Get the parameter types, this includes generic information which is required for GenericEvent + Type[] parameterTypes = method.getGenericParameterTypes(); + if (parameterTypes.length != 1) { + throw new IllegalArgumentException("" + + "Method " + method + " has @SubscribeEvent annotation. " + + "It has " + parameterTypes.length + " arguments, " + + "but event handler methods require a single argument only." + ); + } + + Type parameterType = parameterTypes[0]; + Class eventType; + Class genericType; + + if (parameterType instanceof Class) { // Non-generic event + eventType = (Class) parameterType; + genericType = null; + } else if (parameterType instanceof ParameterizedType) { // Generic event + ParameterizedType parameterizedType = (ParameterizedType) parameterType; + + // Get the event class + Type rawType = parameterizedType.getRawType(); + if (rawType instanceof Class) { + eventType = (Class) rawType; + } else { + throw new UnsupportedOperationException("Raw Type " + rawType.getClass() + " is not supported"); + } + + // Find the type of 'T' in 'GenericEvent' + Type[] typeArguments = parameterizedType.getActualTypeArguments(); + if (typeArguments.length != 1) { + throw new IllegalArgumentException("" + + "Method " + method + " has @SubscribeEvent annotation. " + + "It has a " + eventType + " argument, " + + "but generic events require a single type argument only." + ); + } + + // Get the generic class + Type typeArgument = typeArguments[0]; + if (typeArgument instanceof Class) { + genericType = (Class) typeArgument; + } else { + throw new UnsupportedOperationException("Type Argument " + typeArgument.getClass() + " is not supported"); + } + } else { + throw new UnsupportedOperationException("Parameter Type " + parameterType.getClass() + " is not supported"); + } + + // Ensure 'eventType' is a subclass of event + if (!Event.class.isAssignableFrom(eventType)) { + throw new IllegalArgumentException("" + + "Method " + method + " has @SubscribeEvent annotation, " + + "but takes an argument that is not an Event subtype: " + eventType + ); + } + + return new EventType(eventType, genericType); + } + + private static final class EventType { + private final Class eventType; + private final Class genericType; + + private EventType(Class eventType, Class genericType) { + this.eventType = eventType; + this.genericType = genericType; + } + } + + /** + * Handles casting generics for {@link IEventBus#addListener}. + */ + @SuppressWarnings("unchecked") + private static void addListener(IEventBus eventBus, SubscribeEvent annotation, Class eventType, Consumer consumer) { + eventBus.addListener(annotation.priority(), annotation.receiveCanceled(), (Class) eventType, (Consumer) consumer); + } + +} diff --git a/neoforge/src/main/resources/luckperms.conf b/neoforge/src/main/resources/luckperms.conf new file mode 100644 index 000000000..8d5d47919 --- /dev/null +++ b/neoforge/src/main/resources/luckperms.conf @@ -0,0 +1,641 @@ +#################################################################################################### +# +----------------------------------------------------------------------------------------------+ # +# | __ __ ___ __ __ | # +# | | | | / ` |__/ |__) |__ |__) |\/| /__` | # +# | |___ \__/ \__, | \ | |___ | \ | | .__/ | # +# | | # +# | https://luckperms.net | # +# | | # +# | WIKI: https://luckperms.net/wiki | # +# | DISCORD: https://discord.gg/luckperms | # +# | BUG REPORTS: https://github.com/LuckPerms/LuckPerms/issues | # +# | | # +# | Each option in this file is documented and explained here: | # +# | ==> https://luckperms.net/wiki/Configuration | # +# | | # +# | New options are not added to this file automatically. Default values are used if an | # +# | option cannot be found. The latest config versions can be obtained at the link above. | # +# +----------------------------------------------------------------------------------------------+ # +#################################################################################################### + +# +----------------------------------------------------------------------------------------------+ # +# | | # +# | ESSENTIAL SETTINGS | # +# | | # +# | Important settings that control how LuckPerms functions. | # +# | | # +# +----------------------------------------------------------------------------------------------+ # + +# The name of the server, used for server specific permissions. +# +# - When set to "global" this setting is effectively ignored. +# - In all other cases, the value here is added to all players in a "server" context. +# - See: https://luckperms.net/wiki/Context +server = "global" + +# If the servers own UUID cache/lookup facility should be used when there is no record for a player +# already in LuckPerms. +# +# - When this is set to 'false', commands using a player's username will not work unless the player +# has joined since LuckPerms was first installed. +# - To get around this, you can use a player's uuid directly in the command, or enable this option. +# - When this is set to 'true', the server facility is used. This may use a number of methods, +# including checking the servers local cache, or making a request to the Mojang API. +use-server-uuid-cache = false + + + + +# +----------------------------------------------------------------------------------------------+ # +# | | # +# | STORAGE SETTINGS | # +# | | # +# | Controls which storage method LuckPerms will use to store data. | # +# | | # +# +----------------------------------------------------------------------------------------------+ # + +# How the plugin should store data +# +# - The various options are explained in more detail on the wiki: +# https://luckperms.net/wiki/Storage-types +# +# - Possible options: +# +# | Remote databases - require connection information to be configured below +# |=> MySQL +# |=> MariaDB (preferred over MySQL) +# |=> PostgreSQL +# |=> MongoDB +# +# | Flatfile/local database - don't require any extra configuration +# |=> H2 (preferred over SQLite) +# |=> SQLite +# +# | Readable & editable text files - don't require any extra configuration +# |=> YAML (.yml files) +# |=> JSON (.json files) +# |=> HOCON (.conf files) +# |=> TOML (.toml files) +# | +# | By default, user, group and track data is separated into different files. Data can be combined +# | and all stored in the same file by switching to a combined storage variant. +# | Just add '-combined' to the end of the storage-method, e.g. 'yaml-combined' +# +# - A H2 database is the default option. +# - If you want to edit data manually in "traditional" storage files, we suggest using YAML. +storage-method = "h2" + +# The following block defines the settings for remote database storage methods. +# +# - You don't need to touch any of the settings here if you're using a local storage method! +# - The connection detail options are shared between all remote storage types. +data { + + # Define the address and port for the database. + # - The standard DB engine port is used by default + # (MySQL = 3306, PostgreSQL = 5432, MongoDB = 27017) + # - Specify as "host:port" if differs + address = "localhost" + + # The name of the database to store LuckPerms data in. + # - This must be created already. Don't worry about this setting if you're using MongoDB. + database = "minecraft" + + # Credentials for the database. + username = "root" + password = "" + + # These settings apply to the MySQL connection pool. + # - The default values will be suitable for the majority of users. + # - Do not change these settings unless you know what you're doing! + pool-settings { + + # Sets the maximum size of the MySQL connection pool. + # - Basically this value will determine the maximum number of actual + # connections to the database backend. + # - More information about determining the size of connection pools can be found here: + # https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing + maximum-pool-size = 10 + + # Sets the minimum number of idle connections that the pool will try to maintain. + # - For maximum performance and responsiveness to spike demands, it is recommended to not set + # this value and instead allow the pool to act as a fixed size connection pool. + # (set this value to the same as 'maximum-pool-size') + minimum-idle = 10 + + # This setting controls the maximum lifetime of a connection in the pool in milliseconds. + # - The value should be at least 30 seconds less than any database or infrastructure imposed + # connection time limit. + maximum-lifetime = 1800000 # 30 minutes + + # This setting controls how frequently the pool will 'ping' a connection in order to prevent it + # from being timed out by the database or network infrastructure, measured in milliseconds. + # - The value should be less than maximum-lifetime and greater than 30000 (30 seconds). + # - Setting the value to zero will disable the keepalive functionality. + keepalive-time = 0 + + # This setting controls the maximum number of milliseconds that the plugin will wait for a + # connection from the pool, before timing out. + connection-timeout = 5000 # 5 seconds + + # This setting allows you to define extra properties for connections. + # + # By default, the following options are set to enable utf8 encoding. (you may need to remove + # these if you are using PostgreSQL) + # useUnicode = true + # characterEncoding = "utf8" + # + # You can also use this section to disable SSL connections, by uncommenting the 'useSSL' and + # 'verifyServerCertificate' options below. + properties { + useUnicode = true + characterEncoding = "utf8" + #useSSL: false + #verifyServerCertificate: false + } + } + + # The prefix for all LuckPerms SQL tables. + # + # - This only applies for remote SQL storage types (MySQL, MariaDB, etc). + # - Change this if you want to use different tables for different servers. + table-prefix = "luckperms_" + + # The prefix to use for all LuckPerms MongoDB collections. + # + # - This only applies for the MongoDB storage type. + # - Change this if you want to use different collections for different servers. The default is no + # prefix. + mongodb-collection-prefix = "" + + # The connection string URI to use to connect to the MongoDB instance. + # + # - When configured, this setting will override anything defined in the address, database, + # username or password fields above. + # - If you have a connection string that starts with 'mongodb://' or 'mongodb+srv://', enter it + # below. + # - For more information, please see https://docs.mongodb.com/manual/reference/connection-string/ + mongodb-connection-uri = "" +} + +# Define settings for a "split" storage setup. +# +# - This allows you to define a storage method for each type of data. +# - The connection options above still have to be correct for each type here. +split-storage { + # Don't touch this if you don't want to use split storage! + enabled = false + methods { + # These options don't need to be modified if split storage isn't enabled. + user = "h2" + group = "h2" + track = "h2" + uuid = "h2" + log = "h2" + } +} + + + + +# +----------------------------------------------------------------------------------------------+ # +# | | # +# | UPDATE PROPAGATION & MESSAGING SERVICE | # +# | | # +# | Controls the ways in which LuckPerms will sync data & notify other servers of changes. | # +# | These options are documented on greater detail on the wiki under "Instant Updates". | # +# | | # +# +----------------------------------------------------------------------------------------------+ # + +# This option controls how frequently LuckPerms will perform a sync task. +# +# - A sync task will refresh all data from the storage, and ensure that the most up-to-date data is +# being used by the plugin. +# - This is disabled by default, as most users will not need it. However, if you're using a remote +# storage type without a messaging service setup, you may wish to set this to something like 3. +# - Set to -1 to disable the task completely. +sync-minutes = -1 + +# If the file watcher should be enabled. +# +# - When using a file-based storage type, LuckPerms can monitor the data files for changes, and +# automatically update when changes are detected. +# - If you don't want this feature to be active, set this option to false. +watch-files = true + +# Define which messaging service should be used by the plugin. +# +# - If enabled and configured, LuckPerms will use the messaging service to inform other connected +# servers of changes. +# - Use the command "/lp networksync" to manually push changes. +# - Data is NOT stored using this service. It is only used as a messaging platform. +# +# - If you decide to enable this feature, you should set "sync-minutes" to -1, as there is no need +# for LuckPerms to poll the database for changes. +# +# - Possible options: +# => sql Uses the SQL database to form a queue system for communication. Will only work when +# 'storage-method' is set to MySQL or MariaDB. This is chosen by default if the +# option is set to 'auto' and SQL storage is in use. Set to 'notsql' to disable this. +# => pluginmsg Uses the plugin messaging channels to communicate with the proxy. +# LuckPerms must be installed on your proxy & all connected servers backend servers. +# Won't work if you have more than one proxy. +# => redis Uses Redis pub-sub to push changes. Your server connection info must be configured +# below. +# => rabbitmq Uses RabbitMQ pub-sub to push changes. Your server connection info must be +# configured below. +# => custom Uses a messaging service provided using the LuckPerms API. +# => auto Attempts to automatically setup a messaging service using redis or sql. +messaging-service = "auto" + +# If LuckPerms should automatically push updates after a change has been made with a command. +auto-push-updates = true + +# If LuckPerms should push logging entries to connected servers via the messaging service. +push-log-entries = true + +# If LuckPerms should broadcast received logging entries to players on this platform. +# +# - If you have LuckPerms installed on your backend servers as well as a BungeeCord proxy, you +# should set this option to false on either your backends or your proxies, to avoid players being +# messaged twice about log entries. +broadcast-received-log-entries = true + +# Settings for Redis. +# Port 6379 is used by default; set address to "host:port" if differs +# Multiple Redis nodes can be specified in the same format as a string list under the name "addresses". +redis { + enabled = false + address = "localhost" + username = "" + password = "" +} + +# Settings for RabbitMQ. +# Port 5672 is used by default; set address to "host:port" if differs +rabbitmq { + enabled = false + address = "localhost" + vhost = "/" + username = "guest" + password = "guest" +} + + + + +# +----------------------------------------------------------------------------------------------+ # +# | | # +# | CUSTOMIZATION SETTINGS | # +# | | # +# | Settings that allow admins to customize the way LuckPerms operates. | # +# | | # +# +----------------------------------------------------------------------------------------------+ # + +# Controls how temporary permissions/parents/meta should be accumulated. +# +# - The default behaviour is "deny". +# - This behaviour can also be specified when the command is executed. See the command usage +# documentation for more info. +# +# - Possible options: +# => accumulate durations will be added to the existing expiry time +# => replace durations will be replaced if the new duration is later than the current +# expiration +# => deny the command will just fail if you try to add another node with the same expiry +temporary-add-behaviour = "deny" + +# Controls how LuckPerms will determine a users "primary" group. +# +# - The meaning and influence of "primary groups" are explained in detail on the wiki. +# - The preferred approach is to let LuckPerms automatically determine a users primary group +# based on the relative weight of their parent groups. +# +# - Possible options: +# => stored use the value stored against the users record in the file/database +# => parents-by-weight just use the users most highly weighted parent +# => all-parents-by-weight same as above, but calculates based upon all parents inherited from +# both directly and indirectly +primary-group-calculation = "parents-by-weight" + +# If the plugin should check for "extra" permissions with users run LP commands. +# +# - These extra permissions allow finer control over what users can do with each command, and who +# they have access to edit. +# - The nature of the checks are documented on the wiki under "Argument based command permissions". +# - Argument based permissions are *not* static, unlike the 'base' permissions, and will depend upon +# the arguments given within the command. +argument-based-command-permissions = false + +# If the plugin should check whether senders are a member of a given group before they're able to +# edit the groups data or add/remove other users to/from it. +# Note: these limitations do not apply to the web editor! +require-sender-group-membership-to-modify = false + +# If the plugin should send log notifications to users whenever permissions are modified. +# +# - Notifications are only sent to those with the appropriate permission to receive them +# - They can also be temporarily enabled/disabled on a per-user basis using +# '/lp log notify ' +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_ Selects the value with the highest weight, but only if the +# value was inherited from a group on the given track. +# +# => lowest_on_track_ Same as above, except takes the value with the lowest weight. +# +# => highest_not_on_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_ Same as above, except takes the value with the lowest weight. +# +# => highest_from_group_ Selects the value with the highest weight, but only if the +# value was inherited from the given group. +# +# => lowest_from_group_ Same as above, except takes the value with the lowest weight. +# +# => highest_not_from_group_ Selects the value with the highest weight, but only if the +# value was not inherited from the given group. +# +# => lowest_not_from_group_ Same as above, except takes the value with the lowest weight. +meta-formatting { + prefix { + format = [ + "highest" + ] + duplicates = "first-only" + start-spacer = "" + middle-spacer = " " + end-spacer = "" + } + suffix { + format = [ + "highest" + ] + duplicates = "first-only" + start-spacer = "" + middle-spacer = " " + end-spacer = "" + } +} + + + + +# +----------------------------------------------------------------------------------------------+ # +# | | # +# | PERMISSION CALCULATION AND INHERITANCE | # +# | | # +# | Modify the way permission checks, meta lookups and inheritance resolutions are handled. | # +# | | # +# +----------------------------------------------------------------------------------------------+ # + +# The algorithm LuckPerms should use when traversing the "inheritance tree". +# +# - Possible options: +# => breadth-first See: https://en.wikipedia.org/wiki/Breadth-first_search +# => depth-first-pre-order See: https://en.wikipedia.org/wiki/Depth-first_search +# => depth-first-post-order See: https://en.wikipedia.org/wiki/Depth-first_search +inheritance-traversal-algorithm = "depth-first-pre-order" + +# If a final sort according to "inheritance rules" should be performed after the traversal algorithm +# has resolved the inheritance tree. +# +# "Inheritance rules" refers to things such as group weightings, primary group status, and the +# natural contextual ordering of the group nodes. +# +# Setting this to 'true' will allow for the inheritance rules to take priority over the structure of +# the inheritance tree. +# +# Effectively when this setting is 'true': the tree is flattened, and rules applied afterwards, +# and when this setting is 'false':, the rules are just applied during each step of the traversal. +post-traversal-inheritance-sort = false + +# Defines the mode used to determine whether a set of contexts are satisfied. +# +# - Possible options: +# => at-least-one-value-per-key Set A will be satisfied by another set B, if at least one of the +# key-value entries per key in A are also in B. +# => all-values-per-key Set A will be satisfied by another set B, if all key-value +# entries in A are also in B. +context-satisfy-mode = "at-least-one-value-per-key" + +# LuckPerms has a number of built-in contexts. These can be disabled by adding the context key to +# the list below. +disabled-contexts = [ +# "world" +] + +# +----------------------------------------------------------------------------------------------+ # +# | Permission resolution settings | # +# +----------------------------------------------------------------------------------------------+ # + +# If users on this server should have their global permissions applied. +# When set to false, only server specific permissions will apply for users on this server +include-global = true + +# If users on this server should have their global world permissions applied. +# When set to false, only world specific permissions will apply for users on this server +include-global-world = true + +# If users on this server should have global (non-server specific) groups applied +apply-global-groups = true + +# If users on this server should have global (non-world specific) groups applied +apply-global-world-groups = true + +# +----------------------------------------------------------------------------------------------+ # +# | Meta lookup settings | # +# +----------------------------------------------------------------------------------------------+ # + +# Defines how meta values should be selected. +# +# - Possible options: +# => inheritance Selects the meta value that was inherited first +# => highest-number Selects the highest numerical meta value +# => lowest-number Selects the lowest numerical meta value +meta-value-selection-default = "inheritance" + +# Defines how meta values should be selected per key. +meta-value-selection { + #max-homes = "highest-number" +} + +# +----------------------------------------------------------------------------------------------+ # +# | Inheritance settings | # +# +----------------------------------------------------------------------------------------------+ # + +# If the plugin should apply wildcard permissions. +# +# - If set to true, LuckPerms will detect wildcard permissions, and resolve & apply all registered +# permissions matching the wildcard. +apply-wildcards = true + +# If LuckPerms should resolve and apply permissions according to the Sponge style implicit wildcard +# inheritance system. +# +# - That being: If a user has been granted "example", then the player should have also be +# automatically granted "example.function", "example.another", "example.deeper.nesting", +# and so on. +apply-sponge-implicit-wildcards = false + +# If the plugin should parse regex permissions. +# +# - If set to true, LuckPerms will detect regex permissions, marked with "r=" at the start of the +# node, and resolve & apply all registered permissions matching the regex. +apply-regex = true + +# If the plugin should complete and apply shorthand permissions. +# +# - If set to true, LuckPerms will detect and expand shorthand node patterns. +apply-shorthand = true + +# If the owner of an integrated server should bypass permission checks. +# +# - This setting only applies when LuckPerms is active on a single-player world. +# - The owner of an integrated server is the player whose client instance is running the server. +integrated-server-owner-bypasses-checks = true + +# +----------------------------------------------------------------------------------------------+ # +# | Extra settings | # +# +----------------------------------------------------------------------------------------------+ # + +# A list of context calculators which will be skipped when calculating contexts. +# +# - You can disable context calculators by either: +# => specifying the Java class name used by the calculator (e.g. com.example.ExampleCalculator) +# => specifying a sub-section of the Java package used by the calculator (e.g. com.example) +disabled-context-calculators = [] + +# Allows you to set "aliases" for the worlds sent forward for context calculation. +# +# - These aliases are provided in addition to the real world name. Applied recursively. +# - Remove the comment characters for the default aliases to apply. +world-rewrite { + #world_nether = "world" + #world_the_end = "world" +} + +# Define special group weights for this server. +# +# - Group weights can also be applied directly to group data, using the setweight command. +# - This section allows weights to be set on a per-server basis. +group-weight { + #admin = 10 +} + + + +# +----------------------------------------------------------------------------------------------+ # +# | | # +# | FINE TUNING OPTIONS | # +# | | # +# | A number of more niche settings for tweaking and changing behaviour. The section also | # +# | contains toggles for some more specialised features. It is only necessary to make changes to | # +# | these options if you want to fine-tune LuckPerms behaviour. | # +# | | # +# +----------------------------------------------------------------------------------------------+ # + +# +----------------------------------------------------------------------------------------------+ # +# | Server Operator (OP) settings | # +# +----------------------------------------------------------------------------------------------+ # + +# Controls whether server operators should exist at all. +# +# - When set to 'false', all players will be de-opped, and the /op and /deop commands will be +# disabled. Note that vanilla features like the spawn-protection require an operator on the +# server to work. +enable-ops = true + +# Enables or disables a special permission based system in LuckPerms for controlling OP status. +# +# - If set to true, any user with the permission "luckperms.autoop" will automatically be granted +# server operator status. This permission can be inherited, or set on specific servers/worlds, +# temporarily, etc. +# - Additionally, setting this to true will force the "enable-ops" option above to false. All users +# will be de-opped unless they have the permission node, and the op/deop commands will be +# disabled. +# - It is recommended that you use this option instead of assigning a single '*' permission. +# - However, on Forge this setting can be used as a "pseudo" root wildcard, as many mods support +# the operator system over permissions. +auto-op = false + +# +----------------------------------------------------------------------------------------------+ # +# | Miscellaneous (and rarely used) settings | # +# +----------------------------------------------------------------------------------------------+ # + +# If LuckPerms should produce extra logging output when it handles logins. +# +# - Useful if you're having issues with UUID forwarding or data not being loaded. +debug-logins = false + +# If LuckPerms should allow usernames with non alphanumeric characters. +# +# - Note that due to the design of the storage implementation, usernames must still be 16 characters +# or less. +allow-invalid-usernames = false + +# If LuckPerms should not require users to confirm bulkupdate operations. +# +# - When set to true, operations will be executed immediately. +# - This is not recommended, as bulkupdate has the potential to irreversibly delete large amounts of +# data, and is not designed to be executed automatically. +# - If automation is needed, users should prefer using the LuckPerms API. +skip-bulkupdate-confirmation = false + +# If LuckPerms should prevent bulkupdate operations. +# +# - When set to true, bulkupdate operations (the /lp bulkupdate command) will not work. +# - When set to false, bulkupdate operations will be allowed via the console. +disable-bulkupdate = false + +# If LuckPerms should allow a users primary group to be removed with the 'parent remove' command. +# +# - When this happens, the plugin will set their primary group back to default. +prevent-primary-group-removal = false + +# If LuckPerms should update the list of commands sent to the client when permissions are changed. +update-client-command-list = true + +# If LuckPerms should attempt to resolve Vanilla command target selectors for LP commands. +# See here for more info: https://minecraft.wiki/w/Target_selectors +resolve-command-selectors = false diff --git a/settings.gradle b/settings.gradle index cd050b3c3..db360fbf6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,6 @@ pluginManagement { repositories { + gradlePluginPortal() maven { name = 'Fabric' url = 'https://maven.fabricmc.net/' @@ -8,7 +9,6 @@ pluginManagement { name = 'Forge' url = 'https://maven.minecraftforge.net/' } - gradlePluginPortal() } } @@ -28,6 +28,9 @@ include ( 'bungee', 'bungee:loader', 'fabric', + 'neoforge', + 'neoforge:loader', + 'neoforge:neoforge-api', 'forge', 'forge:loader', 'forge:forge-api',