diff --git a/buildSrc/src/main/kotlin/core-convention.gradle.kts b/buildSrc/src/main/kotlin/core-convention.gradle.kts index 744f7e82d..04abe7f34 100644 --- a/buildSrc/src/main/kotlin/core-convention.gradle.kts +++ b/buildSrc/src/main/kotlin/core-convention.gradle.kts @@ -2,11 +2,13 @@ import org.gradle.accessors.dm.LibrariesForLibs import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension val libs = the() +val javaVersion: String by project plugins { java `java-library` id("publish-convention") + id("java-toolchain-convention") kotlin("jvm") kotlin("plugin.serialization") @@ -40,9 +42,6 @@ ksp { } extensions.configure { - val javaVersion: String by project - - jvmToolchain(javaVersion.toInt()) compilerOptions { freeCompilerArgs = listOf("-Xjsr305=strict") } @@ -70,6 +69,9 @@ tasks { shadowJar { mergeServiceFiles() duplicatesStrategy = DuplicatesStrategy.EXCLUDE + + val relocationPrefix: String by project + relocate("net.kyori.adventure.nbt", "$relocationPrefix.kyori.nbt") } javadoc { diff --git a/buildSrc/src/main/kotlin/java-toolchain-convention.gradle.kts b/buildSrc/src/main/kotlin/java-toolchain-convention.gradle.kts new file mode 100644 index 000000000..7308acb57 --- /dev/null +++ b/buildSrc/src/main/kotlin/java-toolchain-convention.gradle.kts @@ -0,0 +1,18 @@ +import gradle.kotlin.dsl.accessors._bcd9a993373509de50154c5485fe667f.java +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension + +val javaVersion: String by project + +plugins { + java +} + +extensions.findByType()?.apply { + jvmToolchain(javaVersion.toInt()) +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(javaVersion)) + } +} diff --git a/gradle.properties b/gradle.properties index 085844f13..1d392cd53 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,6 +7,6 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled javaVersion=25 mcVersion=1.21.11 group=dev.slne.surf -version=1.21.11-2.56.1 +version=1.21.11-2.57.0 relocationPrefix=dev.slne.surf.surfapi.libs snapshot=false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index db571c018..b1cb7545a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -65,6 +65,7 @@ ktor = "3.4.0" glm = "0.9.9.1-15" ksp-version = "2.3.5" reactor-netty = "1.3.2" +bytebuddy = "1.18.4" # Plugin versions maven-repo-auth = "3.0.4" @@ -166,6 +167,7 @@ ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negoti ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } reactor-netty-core = { module = "io.projectreactor.netty:reactor-netty-core", version.ref = "reactor-netty" } reactor-netty-http = { module = "io.projectreactor.netty:reactor-netty-http", version.ref = "reactor-netty" } +bytebuddy = { module = "net.bytebuddy:byte-buddy", version.ref = "bytebuddy" } # Plugin dependencies kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlinVersion" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 00af0816a..6e750047e 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-snapshots/gradle-9.4.0-20260128061951+0000-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-milestone-5-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/surf-api-bukkit/surf-api-bukkit-api/api/surf-api-bukkit-api.api b/surf-api-bukkit/surf-api-bukkit-api/api/surf-api-bukkit-api.api index 0ca21f8d4..4949f3c65 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/api/surf-api-bukkit-api.api +++ b/surf-api-bukkit/surf-api-bukkit-api/api/surf-api-bukkit-api.api @@ -59,6 +59,23 @@ public final class dev/slne/surf/surfapi/bukkit/api/collections/LazyIntList { public final fun set (II)V } +public final class dev/slne/surf/surfapi/bukkit/api/command/args/AdventureCompoundBinaryTagArgument : dev/jorel/commandapi/arguments/SafeOverrideableArgument { + public fun (Ljava/lang/String;)V + public fun getArgumentType ()Ldev/jorel/commandapi/arguments/CommandAPIArgumentType; + public fun getPrimitiveType ()Ljava/lang/Class; + public synthetic fun parseArgument (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;Ldev/jorel/commandapi/executors/CommandArguments;)Ljava/lang/Object; + public fun parseArgument (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;Ldev/jorel/commandapi/executors/CommandArguments;)Lnet/kyori/adventure/nbt/CompoundBinaryTag; +} + +public final class dev/slne/surf/surfapi/bukkit/api/command/args/AdventureCompoundBinaryTagArgumentKt { + public static final fun adventureCompoundBinaryTagArgument (Ldev/jorel/commandapi/CommandAPICommand;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)Ldev/jorel/commandapi/CommandAPICommand; + public static final fun adventureCompoundBinaryTagArgument (Ldev/jorel/commandapi/CommandTree;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)Ldev/jorel/commandapi/CommandTree; + public static final fun adventureCompoundBinaryTagArgument (Ldev/jorel/commandapi/arguments/Argument;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)Ldev/jorel/commandapi/arguments/Argument; + public static synthetic fun adventureCompoundBinaryTagArgument$default (Ldev/jorel/commandapi/CommandAPICommand;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/jorel/commandapi/CommandAPICommand; + public static synthetic fun adventureCompoundBinaryTagArgument$default (Ldev/jorel/commandapi/CommandTree;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/jorel/commandapi/CommandTree; + public static synthetic fun adventureCompoundBinaryTagArgument$default (Ldev/jorel/commandapi/arguments/Argument;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/jorel/commandapi/arguments/Argument; +} + public class dev/slne/surf/surfapi/bukkit/api/command/args/MiniMessageArgument : dev/jorel/commandapi/arguments/CustomArgument { public fun (Ljava/lang/String;)V } @@ -770,6 +787,22 @@ public final class dev/slne/surf/surfapi/bukkit/api/nms/SurfBukkitNmsBridgeKt { public static final fun getNmsBridge ()Ldev/slne/surf/surfapi/bukkit/api/nms/SurfBukkitNmsBridge; } +public abstract interface class dev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsCommandArgumentTypesBridge { + public static final field Companion Ldev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsCommandArgumentTypesBridge$Companion; + public abstract fun compoundTag ()Lcom/mojang/brigadier/arguments/ArgumentType; + public abstract fun getCompoundTag (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/kyori/adventure/nbt/CompoundBinaryTag; +} + +public final class dev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsCommandArgumentTypesBridge$Companion : dev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsCommandArgumentTypesBridge { + public fun compoundTag ()Lcom/mojang/brigadier/arguments/ArgumentType; + public fun getCompoundTag (Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/kyori/adventure/nbt/CompoundBinaryTag; + public final fun getInstance ()Ldev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsCommandArgumentTypesBridge; +} + +public final class dev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsCommandArgumentTypesBridgeKt { + public static final fun getCommandArgumentTypes ()Ldev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsCommandArgumentTypesBridge; +} + public abstract interface class dev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsCommonBridge { public static final field Companion Ldev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsCommonBridge$Companion; public abstract fun addCompostable (Lorg/bukkit/Material;F)V @@ -801,6 +834,20 @@ public final class dev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsCom public static final fun getNmsCommonBridge ()Ldev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsCommonBridge; } +public abstract interface class dev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsEntityBridge { + public static final field Companion Ldev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsEntityBridge$Companion; + public abstract fun createEntityByNbt (Lorg/bukkit/World;Lorg/bukkit/entity/EntityType;Lio/papermc/paper/math/FinePosition;Lnet/kyori/adventure/nbt/CompoundBinaryTag;)V +} + +public final class dev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsEntityBridge$Companion : dev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsEntityBridge { + public fun createEntityByNbt (Lorg/bukkit/World;Lorg/bukkit/entity/EntityType;Lio/papermc/paper/math/FinePosition;Lnet/kyori/adventure/nbt/CompoundBinaryTag;)V + public final fun getInstance ()Ldev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsEntityBridge; +} + +public final class dev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsEntityBridgeKt { + public static final fun getEntityBridge ()Ldev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsEntityBridge; +} + public abstract interface class dev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsGlowingBridge { public static final field Companion Ldev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsGlowingBridge$Companion; public abstract fun getCurrentFlags (Lorg/bukkit/entity/Entity;)B @@ -1490,7 +1537,9 @@ public final class dev/slne/surf/surfapi/bukkit/api/util/UtilBukkit { public static final fun getCallingPlugin (I)Lorg/bukkit/plugin/java/JavaPlugin; public static synthetic fun getCallingPlugin$default (IILjava/lang/Object;)Lorg/bukkit/plugin/java/JavaPlugin; public static final fun getChunkKey (Lorg/bukkit/Location;)J + public static final fun getChunkX (Lio/papermc/paper/math/Position;)I public static final fun getChunkX (Lorg/bukkit/Location;)I + public static final fun getChunkZ (Lio/papermc/paper/math/Position;)I public static final fun getChunkZ (Lorg/bukkit/Location;)I public static final fun getHighestBlockYAtBlockCoordinates (Lorg/bukkit/ChunkSnapshot;II)I public static final fun getXFromChunkKey (J)I diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/command/args/AdventureCompoundBinaryTagArgument.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/command/args/AdventureCompoundBinaryTagArgument.kt new file mode 100644 index 000000000..07ae107b4 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/command/args/AdventureCompoundBinaryTagArgument.kt @@ -0,0 +1,54 @@ +package dev.slne.surf.surfapi.bukkit.api.command.args + +import com.mojang.brigadier.context.CommandContext +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.CommandTree +import dev.jorel.commandapi.arguments.Argument +import dev.jorel.commandapi.arguments.CommandAPIArgumentType +import dev.jorel.commandapi.arguments.SafeOverrideableArgument +import dev.jorel.commandapi.executors.CommandArguments +import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution +import dev.slne.surf.surfapi.bukkit.api.nms.bridges.commandArgumentTypes +import net.kyori.adventure.nbt.CompoundBinaryTag +import net.kyori.adventure.nbt.TagStringIO + +@OptIn(NmsUseWithCaution::class) +class AdventureCompoundBinaryTagArgument(nodeName: String) : + SafeOverrideableArgument( + nodeName, commandArgumentTypes.compoundTag(), { TagStringIO.tagStringIO().asString(it) } + ) { + override fun getPrimitiveType(): Class { + return CompoundBinaryTag::class.java + } + + override fun getArgumentType(): CommandAPIArgumentType { + return CommandAPIArgumentType.NBT_COMPOUND + } + + override fun parseArgument( + ctx: CommandContext, + key: String, + previousArgs: CommandArguments + ): CompoundBinaryTag { + return commandArgumentTypes.getCompoundTag(ctx, key) + } +} + +inline fun CommandTree.adventureCompoundBinaryTagArgument( + nodeName: String, + optional: Boolean = false, + block: Argument<*>.() -> Unit = {} +): CommandTree = then(AdventureCompoundBinaryTagArgument(nodeName).setOptional(optional).apply(block)) + +inline fun Argument<*>.adventureCompoundBinaryTagArgument( + nodeName: String, + optional: Boolean = false, + block: Argument<*>.() -> Unit = {} +): Argument<*> = then(AdventureCompoundBinaryTagArgument(nodeName).setOptional(optional).apply(block)) + + +inline fun CommandAPICommand.adventureCompoundBinaryTagArgument( + nodeName: String, + optional: Boolean = false, + block: Argument<*>.() -> Unit = {} +): CommandAPICommand = withArguments(AdventureCompoundBinaryTagArgument(nodeName).setOptional(optional).apply(block)) \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsCommandArgumentTypesBridge.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsCommandArgumentTypesBridge.kt new file mode 100644 index 000000000..b0144835a --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsCommandArgumentTypesBridge.kt @@ -0,0 +1,21 @@ +package dev.slne.surf.surfapi.bukkit.api.nms.bridges + +import com.mojang.brigadier.arguments.ArgumentType +import com.mojang.brigadier.context.CommandContext +import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution +import dev.slne.surf.surfapi.core.api.util.requiredService +import net.kyori.adventure.nbt.CompoundBinaryTag + +@NmsUseWithCaution +interface SurfBukkitNmsCommandArgumentTypesBridge { + + fun compoundTag(): ArgumentType<*> + fun getCompoundTag(ctx: CommandContext<*>, key: String): CompoundBinaryTag + + companion object : SurfBukkitNmsCommandArgumentTypesBridge by commandArgumentTypes { + val instance = commandArgumentTypes + } +} + +@NmsUseWithCaution +val commandArgumentTypes = requiredService() \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsEntityBridge.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsEntityBridge.kt new file mode 100644 index 000000000..0644b1f70 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/nms/bridges/SurfBukkitNmsEntityBridge.kt @@ -0,0 +1,23 @@ +package dev.slne.surf.surfapi.bukkit.api.nms.bridges + +import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException +import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution +import dev.slne.surf.surfapi.core.api.util.requiredService +import io.papermc.paper.math.FinePosition +import net.kyori.adventure.nbt.CompoundBinaryTag +import org.bukkit.World +import org.bukkit.entity.EntityType + +@NmsUseWithCaution +interface SurfBukkitNmsEntityBridge { + + @Throws(WrapperCommandSyntaxException::class) + fun createEntityByNbt(world: World, type: EntityType, pos: FinePosition, tag: CompoundBinaryTag) + + companion object : SurfBukkitNmsEntityBridge by entityBridge { + val instance = entityBridge + } +} + +@NmsUseWithCaution +val entityBridge = requiredService() \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/util/bukkit-util.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/util/bukkit-util.kt index 07560de81..8cfc2e7c8 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/util/bukkit-util.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/util/bukkit-util.kt @@ -10,6 +10,7 @@ import dev.slne.surf.surfapi.core.api.util.getCallerClass import dev.slne.surf.surfapi.core.api.util.mutableLong2ObjectMapOf import dev.slne.surf.surfapi.core.api.util.mutableObjectListOf import io.papermc.paper.math.BlockPosition +import io.papermc.paper.math.Position import it.unimi.dsi.fastutil.objects.ObjectList import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -115,6 +116,16 @@ infix fun Location.distanceSqt(other: Location): Double = distanceSquared(other) */ val Location.chunkX get() = blockX shr 4 +/** + * Gets the chunk X coordinate of this position. + */ +val Position.chunkX get() = blockX() shr 4 + +/** + * Gets the chunk Z coordinate of this position. + */ +val Position.chunkZ get() = blockZ() shr 4 + /** * Gets the chunk Z coordinate of this location. */ diff --git a/surf-api-bukkit/surf-api-bukkit-plugin-test/build.gradle.kts b/surf-api-bukkit/surf-api-bukkit-plugin-test/build.gradle.kts index cf8e0f80a..2fadb49a7 100644 --- a/surf-api-bukkit/surf-api-bukkit-plugin-test/build.gradle.kts +++ b/surf-api-bukkit/surf-api-bukkit-plugin-test/build.gradle.kts @@ -50,7 +50,7 @@ tasks { minecraftVersion(findProperty("mcVersion") as String) downloadPlugins { -// hangar("CommandAPI", libs.versions.commandapi.get()) TODO: update to 1.21.11 when released + hangar("CommandAPI", libs.versions.commandapi.get()) modrinth("packetevents", libs.versions.packetevents.plugin.get() + "+spigot") } } diff --git a/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/java/dev/slne/surf/surfapi/bukkit/test/command/SurfApiTestCommand.java b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/java/dev/slne/surf/surfapi/bukkit/test/command/SurfApiTestCommand.java index 2111d881d..3bfd17843 100644 --- a/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/java/dev/slne/surf/surfapi/bukkit/test/command/SurfApiTestCommand.java +++ b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/java/dev/slne/surf/surfapi/bukkit/test/command/SurfApiTestCommand.java @@ -1,20 +1,7 @@ package dev.slne.surf.surfapi.bukkit.test.command; import dev.jorel.commandapi.CommandAPICommand; -import dev.slne.surf.surfapi.bukkit.test.command.subcommands.CommandExceptionTest; -import dev.slne.surf.surfapi.bukkit.test.command.subcommands.GlowingTest; -import dev.slne.surf.surfapi.bukkit.test.command.subcommands.InventoryTest; -import dev.slne.surf.surfapi.bukkit.test.command.subcommands.MaxStacksizeTest; -import dev.slne.surf.surfapi.bukkit.test.command.subcommands.PacketEntityTest; -import dev.slne.surf.surfapi.bukkit.test.command.subcommands.PacketLoreTest; -import dev.slne.surf.surfapi.bukkit.test.command.subcommands.PaginationTest; -import dev.slne.surf.surfapi.bukkit.test.command.subcommands.PrefixConfigTest; -import dev.slne.surf.surfapi.bukkit.test.command.subcommands.ReflectionTest; -import dev.slne.surf.surfapi.bukkit.test.command.subcommands.ScoreboardTest; -import dev.slne.surf.surfapi.bukkit.test.command.subcommands.SmoothTimeSkip; -import dev.slne.surf.surfapi.bukkit.test.command.subcommands.SuspendCommandExecutionTest; -import dev.slne.surf.surfapi.bukkit.test.command.subcommands.ToastTest; -import dev.slne.surf.surfapi.bukkit.test.command.subcommands.VisualizerTest; +import dev.slne.surf.surfapi.bukkit.test.command.subcommands.*; import dev.slne.surf.surfapi.bukkit.test.command.subcommands.gui.InventoryFrameworkTest; public class SurfApiTestCommand extends CommandAPICommand { @@ -38,8 +25,9 @@ public SurfApiTestCommand() { new GlowingTest("glowing"), new PaginationTest("pagination"), new InventoryTest("inventory"), - new ToastTest(("toast")), - new SuspendCommandExecutionTest("suspendCommandExecution") + new ToastTest("toast"), + new SuspendCommandExecutionTest("suspendCommandExecution"), + new SummonCommandTest("summoncommand") ); } } diff --git a/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/subcommands/SummonCommandTest.kt b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/subcommands/SummonCommandTest.kt new file mode 100644 index 000000000..c752fc800 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/subcommands/SummonCommandTest.kt @@ -0,0 +1,24 @@ +package dev.slne.surf.surfapi.bukkit.test.command.subcommands + +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.kotlindsl.entityTypeArgument +import dev.jorel.commandapi.kotlindsl.getValue +import dev.jorel.commandapi.kotlindsl.playerExecutor +import dev.slne.surf.surfapi.bukkit.api.command.args.adventureCompoundBinaryTagArgument +import dev.slne.surf.surfapi.bukkit.api.nms.bridges.entityBridge +import net.kyori.adventure.nbt.CompoundBinaryTag +import org.bukkit.entity.EntityType + +class SummonCommandTest(name: String) : CommandAPICommand(name) { + init { + entityTypeArgument("type") + adventureCompoundBinaryTagArgument("nbt") + + playerExecutor { sender, args -> + val type: EntityType by args + val nbt: CompoundBinaryTag by args + + entityBridge.createEntityByNbt(sender.world, type, sender.location, nbt) + } + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/build.gradle.kts b/surf-api-bukkit/surf-api-bukkit-server/build.gradle.kts index 22b1a6758..04b7d4278 100644 --- a/surf-api-bukkit/surf-api-bukkit-server/build.gradle.kts +++ b/surf-api-bukkit/surf-api-bukkit-server/build.gradle.kts @@ -100,7 +100,6 @@ tasks { shadowJar { val relocationPrefix: String by project relocate("me.devnatan.inventoryframework", "$relocationPrefix.devnatan.inventoryframework") - relocate("net.kyori.adventure.nbt", "$relocationPrefix.kyori.nbt") } } diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/nms/bridges/SurfBukkitNmsCommandArgumentTypesBridgeImpl.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/nms/bridges/SurfBukkitNmsCommandArgumentTypesBridgeImpl.kt new file mode 100644 index 000000000..4836704f6 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/nms/bridges/SurfBukkitNmsCommandArgumentTypesBridgeImpl.kt @@ -0,0 +1,111 @@ +package dev.slne.surf.surfapi.bukkit.server.impl.nms.bridges + +import com.google.auto.service.AutoService +import com.mojang.brigadier.arguments.ArgumentType +import com.mojang.brigadier.context.CommandContext +import com.mojang.brigadier.exceptions.CommandSyntaxException +import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution +import dev.slne.surf.surfapi.bukkit.api.nms.bridges.SurfBukkitNmsCommandArgumentTypesBridge +import dev.slne.surf.surfapi.bukkit.server.nms.AdventureNBT +import dev.slne.surf.surfapi.bukkit.server.reflection.Reflection +import dev.slne.surf.surfapi.core.api.util.checkInstantiationByServiceLoader +import net.bytebuddy.ByteBuddy +import net.bytebuddy.dynamic.loading.ClassLoadingStrategy +import net.bytebuddy.dynamic.scaffold.TypeValidation +import net.bytebuddy.implementation.InvocationHandlerAdapter +import net.bytebuddy.implementation.bind.annotation.RuntimeType +import net.bytebuddy.matcher.ElementMatchers +import net.kyori.adventure.nbt.CompoundBinaryTag +import net.minecraft.commands.arguments.CompoundTagArgument +import java.lang.reflect.InvocationHandler +import java.util.concurrent.ConcurrentHashMap + +@NmsUseWithCaution +@AutoService(SurfBukkitNmsCommandArgumentTypesBridge::class) +class SurfBukkitNmsCommandArgumentTypesBridgeImpl : SurfBukkitNmsCommandArgumentTypesBridge { + init { + checkInstantiationByServiceLoader() + } + + override fun compoundTag(): ArgumentType<*> { + return CompoundTagArgument.compoundTag() + } + + override fun getCompoundTag(ctx: CommandContext<*>, key: String): CompoundBinaryTag { + val nms = CompoundTagArgument.getCompoundTag(ctx, key) + return AdventureNBT.fromNms(nms) + } + + private fun wrap( + base: ArgumentType, + converter: OpenedResultConverter + ): ArgumentType { + val wrappedConverter = OpenedResultConverterImpl.of(converter) + val wrapped = Reflection.VANILLA_ARGUMENT_PROVIDER_IMPL_PROXY.wrap( + Reflection.VANILLA_ARGUMENT_PROVIDER_PROXY.provider(), + base, + wrappedConverter + ) as ArgumentType + + return wrapped + } + + + fun interface OpenedResultConverter { + @Throws(CommandSyntaxException::class) + fun convert(type: T): R + } + + object OpenedResultConverterImpl { + private val converterCache = ConcurrentHashMap() + + @Suppress("UNCHECKED_CAST") + fun of(converter: OpenedResultConverter): Any { + val cacheKey = "${converter.javaClass.name}_${System.identityHashCode(converter)}" + + return converterCache.computeIfAbsent(cacheKey) { + createConverter(converter) + } + } + + private fun createConverter(converter: OpenedResultConverter): Any { + val resultConverterInterface = Class.forName( + "io.papermc.paper.command.brigadier.argument.VanillaArgumentProviderImpl\$ResultConverter" + ) + + val handler = InvocationHandler { _, method, args -> + when (method.name) { + "convert" -> converter.convert(args[0] as B) + else -> throw UnsupportedOperationException("Unknown method: ${method.name}") + } + } + + val dynamicType = ByteBuddy() + .with(TypeValidation.DISABLED) + .subclass(Any::class.java) + .implement(resultConverterInterface) + .name("io.papermc.paper.command.brigadier.argument.GeneratedResultConverter\$${System.nanoTime()}") + .method(ElementMatchers.any()) + .intercept(InvocationHandlerAdapter.of(handler)) + .make() + + val loadedClass = dynamicType.load( + resultConverterInterface.classLoader, + ClassLoadingStrategy.Default.INJECTION + ).loaded + + return loadedClass.getDeclaredConstructor().newInstance() + } + + + class InterceptorHolder( + private val converter: OpenedResultConverter + ) { + @RuntimeType + @Throws(Exception::class) + fun convert(@RuntimeType input: B): C { + return converter.convert(input) + } + } + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/nms/bridges/SurfBukkitNmsEntityBridgeImpl.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/nms/bridges/SurfBukkitNmsEntityBridgeImpl.kt new file mode 100644 index 000000000..22bda85a0 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/nms/bridges/SurfBukkitNmsEntityBridgeImpl.kt @@ -0,0 +1,47 @@ +package dev.slne.surf.surfapi.bukkit.server.impl.nms.bridges + +import ca.spottedleaf.moonrise.common.util.TickThread +import com.google.auto.service.AutoService +import com.mojang.brigadier.exceptions.CommandSyntaxException +import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException +import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution +import dev.slne.surf.surfapi.bukkit.api.nms.bridges.SurfBukkitNmsEntityBridge +import dev.slne.surf.surfapi.bukkit.api.util.chunkX +import dev.slne.surf.surfapi.bukkit.api.util.chunkZ +import dev.slne.surf.surfapi.bukkit.server.nms.AdventureNBT +import dev.slne.surf.surfapi.bukkit.server.nms.toNms +import dev.slne.surf.surfapi.bukkit.server.nms.toNmsHolder +import dev.slne.surf.surfapi.core.api.util.checkInstantiationByServiceLoader +import io.papermc.paper.math.FinePosition +import net.kyori.adventure.nbt.CompoundBinaryTag +import net.minecraft.server.MinecraftServer +import net.minecraft.server.commands.SummonCommand +import org.bukkit.World +import org.bukkit.entity.EntityType + +@NmsUseWithCaution +@AutoService(SurfBukkitNmsEntityBridge::class) +class SurfBukkitNmsEntityBridgeImpl : SurfBukkitNmsEntityBridge { + init { + checkInstantiationByServiceLoader() + } + + override fun createEntityByNbt( + world: World, + type: EntityType, + pos: FinePosition, + tag: CompoundBinaryTag + ) { + val worldNMS = world.toNms() + TickThread.ensureTickThread(worldNMS, pos.chunkX, pos.chunkZ, "Cannot create entity asynchronously") + + val source = MinecraftServer.getServer().createCommandSourceStack() + .withLevel(worldNMS) + + try { + SummonCommand.createEntity(source, type.toNmsHolder(), pos.toNms(), AdventureNBT.toNms(tag), false) + } catch (e: CommandSyntaxException) { + throw WrapperCommandSyntaxException(e) + } + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/nms/AdventureNBT.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/nms/AdventureNBT.kt new file mode 100644 index 000000000..8fee594e7 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/nms/AdventureNBT.kt @@ -0,0 +1,96 @@ +package dev.slne.surf.surfapi.bukkit.server.nms + +import net.kyori.adventure.nbt.* +import net.minecraft.nbt.* + +object AdventureNBT { + fun toNms( + adventure: CompoundBinaryTag, + accounter: NbtAccounter = NbtAccounter.unlimitedHeap() + ): CompoundTag { + return CompoundTag().also { tag -> + for ((key, value) in adventure) { + tag.put(key, toNms(value, accounter)) + } + } + } + + fun toNms( + adventure: BinaryTag, + accounter: NbtAccounter = NbtAccounter.unlimitedHeap() + ): Tag { + accounter.pushDepth() + + val nmsTag = when (adventure) { + is IntBinaryTag -> IntTag.valueOf(adventure.value()) + is ByteBinaryTag -> ByteTag.valueOf(adventure.value()) + is FloatBinaryTag -> FloatTag.valueOf(adventure.value()) + is LongBinaryTag -> LongTag.valueOf(adventure.value()) + is DoubleBinaryTag -> DoubleTag.valueOf(adventure.value()) + is ShortBinaryTag -> ShortTag.valueOf(adventure.value()) + is StringBinaryTag -> StringTag.valueOf(adventure.value()) + is ByteArrayBinaryTag -> ByteArrayTag(adventure.value()) + is IntArrayBinaryTag -> IntArrayTag(adventure.value()) + is LongArrayBinaryTag -> LongArrayTag(adventure.value()) + is EndBinaryTag -> EndTag.INSTANCE + is ListBinaryTag -> if (adventure.isEmpty) { + ListTag() + } else { + val list = ListTag() + for (entry in adventure) { + val nms = toNms(entry) + list.add(nms) + } + list + } + + is CompoundBinaryTag -> { + val tag = CompoundTag() + for ((key, entry) in adventure) { + val nms = toNms(entry) + tag.put(key, nms) + } + tag + } + + else -> throw IllegalArgumentException("Unsupported tag type: ${adventure::class}") + } + + accounter.popDepth() + + return nmsTag + } + + fun fromNms(nms: CompoundTag): CompoundBinaryTag = CompoundBinaryTag.builder().also { builder -> + nms.forEach { key, tag -> builder.put(key, fromNms(tag)) } + }.build() + + fun fromNms(nms: Tag): BinaryTag = when (nms) { + is IntTag -> IntBinaryTag.intBinaryTag(nms.intValue()) + is ByteTag -> ByteBinaryTag.byteBinaryTag(nms.byteValue()) + is FloatTag -> FloatBinaryTag.floatBinaryTag(nms.floatValue()) + is LongTag -> LongBinaryTag.longBinaryTag(nms.longValue()) + is DoubleTag -> DoubleBinaryTag.doubleBinaryTag(nms.doubleValue()) + is ShortTag -> ShortBinaryTag.shortBinaryTag(nms.shortValue()) + is StringTag -> StringBinaryTag.stringBinaryTag(nms.value()) + is ByteArrayTag -> ByteArrayBinaryTag.byteArrayBinaryTag(*nms.asByteArray) + is IntArrayTag -> IntArrayBinaryTag.intArrayBinaryTag(*nms.asIntArray) + is LongArrayTag -> LongArrayBinaryTag.longArrayBinaryTag(*nms.asLongArray) + is EndTag -> EndBinaryTag.endBinaryTag() + is ListTag -> { + val tag = ListBinaryTag.heterogeneousListBinaryTag() + for (t in nms) { + tag.add(fromNms(t)) + } + tag.build() + } + + is CompoundTag -> { + val adventure = CompoundBinaryTag.builder() + nms.forEach { key, tag -> + adventure.put(key, fromNms(tag)) + } + adventure.build() + } + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/nms/nms-extensions.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/nms/nms-extensions.kt index c39252d52..ee14b83a6 100644 --- a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/nms/nms-extensions.kt +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/nms/nms-extensions.kt @@ -12,6 +12,7 @@ import io.papermc.paper.math.FinePosition import io.papermc.paper.math.Position import net.minecraft.advancements.AdvancementType import net.minecraft.core.BlockPos +import net.minecraft.core.Holder import net.minecraft.network.chat.Component import net.minecraft.server.level.ServerLevel import net.minecraft.server.level.ServerPlayer @@ -128,6 +129,9 @@ val craftServer: CraftServer get() = server.toCraft() val commodore: Commodore get() = CraftMagicNumbers.INSTANCE.commodore fun EntityType.toNms(): net.minecraft.world.entity.EntityType<*> = CraftEntityType.bukkitToMinecraft(this) +fun EntityType.toNmsHolder() = + CraftEntityType.bukkitToMinecraftHolder(this) as Holder.Reference> + fun World.toNms(): ServerLevel = (this as CraftWorld).handle fun Entity.toNms(): net.minecraft.world.entity.Entity = (this as CraftEntity).handle fun LivingEntity.toNms(): net.minecraft.world.entity.LivingEntity = (this as CraftLivingEntity).handle diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/reflection/Reflection.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/reflection/Reflection.kt index 02aece6a2..a0b28e04c 100644 --- a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/reflection/Reflection.kt +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/reflection/Reflection.kt @@ -12,6 +12,8 @@ object Reflection { val ENTITY_PROXY: EntityProxy val SERVER_CONNECTION_LISTENER_PROXY: ServerConnectionListenerProxy val JAVA_PLUGIN_PROXY: JavaPluginProxy + val VANILLA_ARGUMENT_PROVIDER_IMPL_PROXY: VanillaArgumentProviderImplProxy + val VANILLA_ARGUMENT_PROVIDER_PROXY: VanillaArgumentProviderProxy init { val remapper = ReflectionRemapper.forReobfMappingsInPaperJar() @@ -23,6 +25,8 @@ object Reflection { ENTITY_PROXY = proxyFactory.reflectionProxy() SERVER_CONNECTION_LISTENER_PROXY = proxyFactory.reflectionProxy() JAVA_PLUGIN_PROXY = surfReflection.createProxy() + VANILLA_ARGUMENT_PROVIDER_IMPL_PROXY = surfReflection.createProxy() + VANILLA_ARGUMENT_PROVIDER_PROXY = surfReflection.createProxy() // gc the remapper System.gc() diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/reflection/VanillaArgumentProviderImplProxy.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/reflection/VanillaArgumentProviderImplProxy.kt new file mode 100644 index 000000000..9e2c92004 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/reflection/VanillaArgumentProviderImplProxy.kt @@ -0,0 +1,15 @@ +package dev.slne.surf.surfapi.bukkit.server.reflection + +import com.mojang.brigadier.arguments.ArgumentType +import com.mojang.brigadier.exceptions.CommandSyntaxException +import dev.slne.surf.surfapi.core.api.reflection.Name +import dev.slne.surf.surfapi.core.api.reflection.SurfProxy +import io.papermc.paper.command.brigadier.argument.VanillaArgumentProviderImpl + +@SurfProxy(VanillaArgumentProviderImpl::class) +interface VanillaArgumentProviderImplProxy { + + @Name("wrap") + @Throws(CommandSyntaxException::class) + fun wrap(instance: Any, base: Any, converter: Any): ArgumentType<*> +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/reflection/VanillaArgumentProviderProxy.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/reflection/VanillaArgumentProviderProxy.kt new file mode 100644 index 000000000..ac04ce6a5 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/reflection/VanillaArgumentProviderProxy.kt @@ -0,0 +1,13 @@ +package dev.slne.surf.surfapi.bukkit.server.reflection + +import dev.slne.surf.surfapi.core.api.reflection.Name +import dev.slne.surf.surfapi.core.api.reflection.Static +import dev.slne.surf.surfapi.core.api.reflection.SurfProxy + +@SurfProxy(qualifiedName = "io.papermc.paper.command.brigadier.argument.VanillaArgumentProvider") +interface VanillaArgumentProviderProxy { + + @Static + @Name("provider") + fun provider(): Any +} \ No newline at end of file diff --git a/surf-api-core/surf-api-core-server/build.gradle.kts b/surf-api-core/surf-api-core-server/build.gradle.kts index ccf9857b4..96464876c 100644 --- a/surf-api-core/surf-api-core-server/build.gradle.kts +++ b/surf-api-core/surf-api-core-server/build.gradle.kts @@ -6,6 +6,7 @@ dependencies { api(project(":surf-api-core:surf-api-core-api")) api(project(":surf-api-shared:surf-api-shared-internal")) compileOnly(libs.packetevents.netty.common) + api(libs.bytebuddy) } description = "surf-api-core-server" diff --git a/surf-api-core/surf-api-core-server/src/main/java/dev/slne/surf/surfapi/core/server/impl/reflection/SurfInvocationHandlerJava.java b/surf-api-core/surf-api-core-server/src/main/java/dev/slne/surf/surfapi/core/server/impl/reflection/SurfInvocationHandlerJava.java index 8c345f672..8cdb75189 100644 --- a/surf-api-core/surf-api-core-server/src/main/java/dev/slne/surf/surfapi/core/server/impl/reflection/SurfInvocationHandlerJava.java +++ b/surf-api-core/surf-api-core-server/src/main/java/dev/slne/surf/surfapi/core/server/impl/reflection/SurfInvocationHandlerJava.java @@ -29,252 +29,287 @@ @NullMarked public final class SurfInvocationHandlerJava implements InvocationHandler { - private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); - private static final Object[] EMPTY_ARGS = new Object[0]; - - private final Class proxyClass; - private final Class proxiedClass; - private final Object2ObjectMap cache; - private final Map defaultCache = new ConcurrentHashMap<>(); - - public SurfInvocationHandlerJava(Class proxyClass, Class proxiedClass) { - this.proxyClass = proxyClass; - this.proxiedClass = proxiedClass; - this.cache = buildCache(); - } - - @Override - public @Nullable Object invoke(final Object proxy, final Method method, - final Object @Nullable [] args) - throws Throwable { - if (isEqualsMethod(method)) { - return Objects.equals(proxy, Objects.requireNonNull(args)[0]); - } else if (isHashCodeMethod(method)) { - return System.identityHashCode(proxy); - } else if (isToStringMethod(method)) { - return ToStringBuilder.reflectionToString(proxy); - } else if (method.isDefault()) { - return invokeDefault(proxy, method, args); - } else { - final Invokable invokable = cache.get(method); - if (invokable == null) { - throw new IllegalStateException("No handler cached for " + method.getName()); - } else { - return invokable.invoke(args != null ? args : EMPTY_ARGS); - } + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + private static final Object[] EMPTY_ARGS = new Object[0]; + + private final Class proxyClass; + private final Class proxiedClass; + private final Object2ObjectMap cache; + private final Map defaultCache = new ConcurrentHashMap<>(); + + public SurfInvocationHandlerJava(Class proxyClass, Class proxiedClass) { + this.proxyClass = proxyClass; + this.proxiedClass = proxiedClass; + this.cache = buildCache(); } - } - - private @Nullable Object invokeDefault(final Object proxy, final Method method, - final Object @Nullable [] args) - throws Throwable { - final MethodHandle handle = defaultCache.computeIfAbsent(method, - m -> sneaky(() -> normalizeMethodHandleType( - MethodHandles.privateLookupIn(proxyClass, LOOKUP) - .findSpecial( - proxyClass, - m.getName(), - MethodType.methodType(method.getReturnType(), method.getParameterTypes()), - proxyClass - ) - .bindTo(proxy) - ))); - - return args == null ? handle.invokeExact() : handle.invokeExact(args); - } - - private Object2ObjectMap buildCache() { - return Arrays.stream(proxyClass.getDeclaredMethods()) - .filter(m -> !m.isSynthetic()) - .filter(m -> !m.isDefault()) - .filter(m -> !isEqualsHashOrToStringMethod(m)) - .collect(Collectors.toMap(Function.identity(), this::createInvokable, (e1, e2) -> e1, - Object2ObjectOpenHashMap::new)); - } - - private Invokable createInvokable(final Method method) { - final var fieldAnnotation = method.getDeclaredAnnotation( - dev.slne.surf.surfapi.core.api.reflection.Field.class); - final var staticAnnotation = method.getDeclaredAnnotation(Static.class); - final var constructorAnnotation = method.getDeclaredAnnotation( - dev.slne.surf.surfapi.core.api.reflection.Constructor.class); - final var nameAnnotation = method.getDeclaredAnnotation(Name.class); - final var privateLookup = sneaky(() -> MethodHandles.privateLookupIn(proxiedClass, LOOKUP)); - - if (fieldAnnotation != null) { - final String fieldName = getMethodName(method, nameAnnotation, fieldAnnotation, - staticAnnotation, constructorAnnotation); - final Field field = sneaky(() -> findField(proxiedClass, fieldName)); - final boolean isGetter = fieldAnnotation.type() == Type.GETTER; - final MethodHandle handleGetter = - isGetter ? sneaky(() -> privateLookup.unreflectGetter(field)) : null; - final MethodHandle handleSetter = !isGetter && !fieldAnnotation.overrideFinal() - ? sneaky(() -> privateLookup.unreflectSetter(field)) : null; - - if (isGetter) { - checkParamCount(method, staticAnnotation != null ? 0 : 1); - return new HandleInvokable(normalizeMethodHandleType(handleGetter)); - } else { - if (fieldAnnotation.overrideFinal()) { - checkParamCount(method, staticAnnotation != null ? 1 : 2); - return new ReflectionSetterInvokable(field, staticAnnotation != null); + + @Override + public @Nullable Object invoke(final Object proxy, final Method method, + final Object @Nullable [] args) + throws Throwable { + if (isEqualsMethod(method)) { + return Objects.equals(proxy, Objects.requireNonNull(args)[0]); + } else if (isHashCodeMethod(method)) { + return System.identityHashCode(proxy); + } else if (isToStringMethod(method)) { + return ToStringBuilder.reflectionToString(proxy); + } else if (method.isDefault()) { + return invokeDefault(proxy, method, args); + } else { + final Invokable invokable = cache.get(method); + if (invokable == null) { + throw new IllegalStateException("No handler cached for " + method.getName()); + } else { + return invokable.invoke(args != null ? args : EMPTY_ARGS); + } } + } - checkParamCount(method, staticAnnotation != null ? 1 : 2); - return new HandleInvokable(normalizeMethodHandleType(handleSetter)); - } + private @Nullable Object invokeDefault(final Object proxy, final Method method, + final Object @Nullable [] args) + throws Throwable { + final MethodHandle handle = defaultCache.computeIfAbsent(method, + m -> sneaky(() -> normalizeMethodHandleType( + MethodHandles.privateLookupIn(proxyClass, LOOKUP) + .findSpecial( + proxyClass, + m.getName(), + MethodType.methodType(method.getReturnType(), method.getParameterTypes()), + proxyClass + ) + .bindTo(proxy) + ))); + + return args == null ? handle.invokeExact() : handle.invokeExact(args); } - if (constructorAnnotation != null) { - final var handle = sneaky( - () -> privateLookup.unreflectConstructor(findConstructor(proxiedClass, method))); - return new HandleInvokable(normalizeMethodHandleType(handle)); + private Object2ObjectMap buildCache() { + return Arrays.stream(proxyClass.getDeclaredMethods()) + .filter(m -> !m.isSynthetic()) + .filter(m -> !m.isDefault()) + .filter(m -> !isEqualsHashOrToStringMethod(m)) + .collect(Collectors.toMap(Function.identity(), this::createInvokable, (e1, e2) -> e1, + Object2ObjectOpenHashMap::new)); } - if (staticAnnotation == null && method.getParameterCount() == 0) { - throw new IllegalStateException( - "Instance method '" + method.getName() + "' must have a receiver parameter"); + private Invokable createInvokable(final Method method) { + final var fieldAnnotation = method.getDeclaredAnnotation( + dev.slne.surf.surfapi.core.api.reflection.Field.class); + final var staticAnnotation = method.getDeclaredAnnotation(Static.class); + final var constructorAnnotation = method.getDeclaredAnnotation( + dev.slne.surf.surfapi.core.api.reflection.Constructor.class); + final var nameAnnotation = method.getDeclaredAnnotation(Name.class); + final var privateLookup = sneaky(() -> MethodHandles.privateLookupIn(proxiedClass, LOOKUP)); + + if (fieldAnnotation != null) { + final String fieldName = getMethodName(method, nameAnnotation, fieldAnnotation, + staticAnnotation, constructorAnnotation); + final Field field = sneaky(() -> findField(proxiedClass, fieldName)); + final boolean isGetter = fieldAnnotation.type() == Type.GETTER; + final MethodHandle handleGetter = + isGetter ? sneaky(() -> privateLookup.unreflectGetter(field)) : null; + final MethodHandle handleSetter = !isGetter && !fieldAnnotation.overrideFinal() + ? sneaky(() -> privateLookup.unreflectSetter(field)) : null; + + if (isGetter) { + checkParamCount(method, staticAnnotation != null ? 0 : 1); + final boolean hasParams = staticAnnotation == null; // instance parameter + return new HandleInvokable(normalizeMethodHandleType(handleGetter), hasParams); + } else { + if (fieldAnnotation.overrideFinal()) { + checkParamCount(method, staticAnnotation != null ? 1 : 2); + return new ReflectionSetterInvokable(field, staticAnnotation != null); + } + + checkParamCount(method, staticAnnotation != null ? 1 : 2); + final boolean hasParams = true; // setter always has params + return new HandleInvokable(normalizeMethodHandleType(handleSetter), hasParams); + } + } + + if (constructorAnnotation != null) { + final var handle = sneaky( + () -> privateLookup.unreflectConstructor(findConstructor(proxiedClass, method))); + final boolean hasParams = handle.type().parameterCount() > 0; + return new HandleInvokable(normalizeMethodHandleType(handle), hasParams); + } + + if (staticAnnotation == null && method.getParameterCount() == 0) { + throw new IllegalStateException( + "Instance method '" + method.getName() + "' must have a receiver parameter"); + } + + final Method target = sneaky( + () -> findMethod(proxiedClass, method, nameAnnotation, staticAnnotation)); + final MethodHandle handle = sneaky(() -> privateLookup.unreflect(target)); + final boolean hasParams = handle.type().parameterCount() > 0; + return new HandleInvokable(normalizeMethodHandleType(handle), hasParams); } - final Method target = sneaky( - () -> findMethod(proxiedClass, method, nameAnnotation, staticAnnotation)); - final MethodHandle handle = sneaky(() -> privateLookup.unreflect(target)); - return new HandleInvokable(normalizeMethodHandleType(handle)); - } - - private static MethodHandle normalizeMethodHandleType(final MethodHandle handle) { - if (handle.type().parameterCount() == 0) { - return handle.asType(MethodType.methodType(Object.class)); - } else { - return handle.asSpreader(Object[].class, handle.type().parameterCount()) - .asType(MethodType.methodType(Object.class, Object[].class)); + private static MethodHandle normalizeMethodHandleType(final MethodHandle handle) { + if (handle.type().parameterCount() == 0) { + return handle.asType(MethodType.methodType(Object.class)); + } else { + return handle.asSpreader(Object[].class, handle.type().parameterCount()) + .asType(MethodType.methodType(Object.class, Object[].class)); + } } - } - private static Field findField(final Class clazz, final String name) - throws NoSuchFieldException { - final Field field = FieldUtils.getField(clazz, name, true); - if (field == null) { - throw new NoSuchFieldException(name); + private static Field findField(final Class clazz, final String name) + throws NoSuchFieldException { + final Field field = FieldUtils.getField(clazz, name, true); + if (field == null) { + throw new NoSuchFieldException(name); + } + return field; } - return field; - } - - private static Constructor findConstructor(final Class clazz, final Method method) - throws NoSuchMethodException { - final Constructor constructor = clazz.getDeclaredConstructor(method.getParameterTypes()); - constructor.setAccessible(true); - return constructor; - } - - - private static Method findMethod( - final Class clazz, - final Method original, - final Name nameAnnotation, - final @Nullable Static staticAnnotation - ) throws NoSuchMethodException { - final int paramOffset = staticAnnotation == null ? 1 : 0; // instance param first if not static - final Class[] paramTypes = Arrays.copyOfRange(original.getParameterTypes(), paramOffset, - original.getParameterCount()); - final String methodName = getMethodName(original, nameAnnotation, null, staticAnnotation, null); - final Method method = MethodUtils.getMatchingMethod(clazz, methodName, paramTypes); - if (method != null) { - method.setAccessible(true); + + private static Constructor findConstructor(final Class clazz, final Method method) + throws NoSuchMethodException { + final Constructor constructor = clazz.getDeclaredConstructor(method.getParameterTypes()); + constructor.setAccessible(true); + return constructor; } - if (method == null) { - throw new NoSuchMethodException( - "Method " + methodName + " with params " + Arrays.toString(paramTypes)); + + private static Method findMethod( + final Class clazz, + final Method original, + final Name nameAnnotation, + final @Nullable Static staticAnnotation + ) throws NoSuchMethodException { + final int paramOffset = staticAnnotation == null ? 1 : 0; // instance param first if not static + final Class[] paramTypes = Arrays.copyOfRange(original.getParameterTypes(), paramOffset, + original.getParameterCount()); + final String methodName = getMethodName(original, nameAnnotation, null, staticAnnotation, null); + Method method = MethodUtils.getMatchingMethod(clazz, methodName, paramTypes); + + if (method == null && isAllObjectParams(paramTypes)) { + method = findMethodByNameAndParamCount(clazz, methodName, paramTypes.length); + } + + if (method != null) { + method.setAccessible(true); + } + + if (method == null) { + throw new NoSuchMethodException( + "Method '" + methodName + "' with params " + Arrays.toString(paramTypes)); + } + + return method; } - return method; - } - - - private static String getMethodName( - final Method method, - final @Nullable Name nameAnnotation, - final dev.slne.surf.surfapi.core.api.reflection.@Nullable Field fieldAnnotation, - final @Nullable Static staticAnnotation, - final dev.slne.surf.surfapi.core.api.reflection.@Nullable Constructor constructorAnnotation - ) { - // when block converted to if-else structure - if (nameAnnotation != null && !nameAnnotation.value().isBlank()) { - return nameAnnotation.value(); - } else if (fieldAnnotation != null && !fieldAnnotation.name().isBlank()) { - return fieldAnnotation.name(); - } else if (staticAnnotation != null && !staticAnnotation.name().isBlank()) { - return staticAnnotation.name(); - } else if (constructorAnnotation != null) { - return method.getReturnType().getSimpleName(); - } else { - return method.getName(); + + private static @Nullable Method findMethodByNameAndParamCount( + final Class clazz, + final String methodName, + final int paramCount + ) { + for (Method method : clazz.getDeclaredMethods()) { + if (method.getName().equals(methodName) && method.getParameterCount() == paramCount) { + return method; + } + } + return null; } - } - private static void checkParamCount(final Method method, final int expected) { - if (method.getParameterCount() != expected) { - throw new IllegalStateException( - "Method " + method.getName() + " must have " + expected + " parameters, found " - + method.getParameterCount()); + private static boolean isAllObjectParams(final Class[] paramTypes) { + for (Class type : paramTypes) { + if (type != Object.class) { + return false; + } + } + return true; } - } - private static boolean isEqualsMethod(final Method method) { - return "equals".equals(method.getName()) && method.getParameterCount() == 1 - && method.getParameterTypes()[0] == Object.class; - } + private static String getMethodName( + final Method method, + final @Nullable Name nameAnnotation, + final dev.slne.surf.surfapi.core.api.reflection.@Nullable Field fieldAnnotation, + final @Nullable Static staticAnnotation, + final dev.slne.surf.surfapi.core.api.reflection.@Nullable Constructor constructorAnnotation + ) { + // when block converted to if-else structure + if (nameAnnotation != null && !nameAnnotation.value().isBlank()) { + return nameAnnotation.value(); + } else if (fieldAnnotation != null && !fieldAnnotation.name().isBlank()) { + return fieldAnnotation.name(); + } else if (staticAnnotation != null && !staticAnnotation.name().isBlank()) { + return staticAnnotation.name(); + } else if (constructorAnnotation != null) { + return method.getReturnType().getSimpleName(); + } else { + return method.getName(); + } + } - private static boolean isHashCodeMethod(final Method method) { - return "hashCode".equals(method.getName()) && method.getParameterCount() == 0; - } + private static void checkParamCount(final Method method, final int expected) { + if (method.getParameterCount() != expected) { + throw new IllegalStateException( + "Method " + method.getName() + " must have " + expected + " parameters, found " + + method.getParameterCount()); + } + } - private static boolean isToStringMethod(final Method method) { - return "toString".equals(method.getName()) && method.getParameterCount() == 0; - } + private static boolean isEqualsMethod(final Method method) { + return "equals".equals(method.getName()) && method.getParameterCount() == 1 + && method.getParameterTypes()[0] == Object.class; + } - private static boolean isEqualsHashOrToStringMethod(final Method method) { - return isEqualsMethod(method) || isHashCodeMethod(method) || isToStringMethod(method); - } + private static boolean isHashCodeMethod(final Method method) { + return "hashCode".equals(method.getName()) && method.getParameterCount() == 0; + } - private sealed interface Invokable permits HandleInvokable, ReflectionSetterInvokable { + private static boolean isToStringMethod(final Method method) { + return "toString".equals(method.getName()) && method.getParameterCount() == 0; + } - @Nullable - Object invoke(final Object[] args) throws Throwable; - } + private static boolean isEqualsHashOrToStringMethod(final Method method) { + return isEqualsMethod(method) || isHashCodeMethod(method) || isToStringMethod(method); + } + private sealed interface Invokable permits HandleInvokable, ReflectionSetterInvokable { - private record HandleInvokable(MethodHandle handle) implements Invokable { + @Nullable + Object invoke(final Object[] args) throws Throwable; + } - @Override - public @Nullable Object invoke(final Object[] args) throws Throwable { - return handle.invokeExact(args); + + private record HandleInvokable(MethodHandle handle, boolean hasParams) implements Invokable { + + @Override + public @Nullable Object invoke(final Object[] args) throws Throwable { + if (hasParams) { + return handle.invokeExact(args); + } else { + return handle.invokeExact(); + } + } } - } - private record ReflectionSetterInvokable(Field field, boolean isStatic) implements Invokable { + private record ReflectionSetterInvokable(Field field, boolean isStatic) implements Invokable { - @Override - public @Nullable Object invoke(Object[] args) throws Throwable { - if (isStatic) { - SurfUtil.setStaticFinalField(field, args[0]); - } else { - SurfUtil.setFinalField(field, args[0], args[1]); - } - return null; + @Override + public @Nullable Object invoke(Object[] args) throws Throwable { + if (isStatic) { + SurfUtil.setStaticFinalField(field, args[0]); + } else { + SurfUtil.setFinalField(field, args[0], args[1]); + } + return null; + } } - } - private static T sneaky(final ExceptionalSupplier supplier) { - try { - return supplier.get(); - } catch (Throwable e) { - throw new RuntimeException(e); + private static T sneaky(final ExceptionalSupplier supplier) { + try { + return supplier.get(); + } catch (Throwable e) { + throw new RuntimeException(e); + } } - } - @FunctionalInterface - private interface ExceptionalSupplier { + @FunctionalInterface + private interface ExceptionalSupplier { - T get() throws Throwable; - } + T get() throws Throwable; + } } diff --git a/surf-api-gradle-plugin/build.gradle.kts b/surf-api-gradle-plugin/build.gradle.kts index d0db5c457..708be82d0 100644 --- a/surf-api-gradle-plugin/build.gradle.kts +++ b/surf-api-gradle-plugin/build.gradle.kts @@ -11,6 +11,7 @@ val snapshot = (findProperty("snapshot") as String).toBooleanStrict() plugins { `java-library` `kotlin-dsl` + `java-toolchain-convention` id("com.gradle.plugin-publish") version "1.3.1" kotlin("plugin.serialization") diff --git a/surf-api-gradle-plugin/surf-api-processor/build.gradle.kts b/surf-api-gradle-plugin/surf-api-processor/build.gradle.kts index 1117bcc20..c5bd4f0dd 100644 --- a/surf-api-gradle-plugin/surf-api-processor/build.gradle.kts +++ b/surf-api-gradle-plugin/surf-api-processor/build.gradle.kts @@ -8,6 +8,7 @@ plugins { kotlin("jvm") kotlin("plugin.serialization") `publish-convention` + `java-toolchain-convention` } group = groupId diff --git a/surf-api-standalone/build.gradle.kts b/surf-api-standalone/build.gradle.kts index 821137e50..9826e57f2 100644 --- a/surf-api-standalone/build.gradle.kts +++ b/surf-api-standalone/build.gradle.kts @@ -32,12 +32,5 @@ dependencies { runtimeOnly(libs.flogger.slf4j.backend) } -tasks { - shadowJar { - val relocationPrefix: String by project - relocate("net.kyori.adventure.nbt", "$relocationPrefix.kyori.nbt") - } -} - private fun T.exclude(provider: Provider) = provider.get().module.apply { exclude(group, name) } \ No newline at end of file diff --git a/surf-api-velocity/surf-api-velocity-server/build.gradle.kts b/surf-api-velocity/surf-api-velocity-server/build.gradle.kts index 22336be17..407523758 100644 --- a/surf-api-velocity/surf-api-velocity-server/build.gradle.kts +++ b/surf-api-velocity/surf-api-velocity-server/build.gradle.kts @@ -23,7 +23,6 @@ tasks { shadowJar { val relocationPrefix: String by project relocate("it.unimi.dsi.fastutil", "$relocationPrefix.fastutil") - relocate("net.kyori.adventure.nbt", "$relocationPrefix.kyori.nbt") } }