diff --git a/build.gradle.kts b/build.gradle.kts index 85f78e3d..e318b417 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,19 +7,17 @@ buildscript { maven("https://repo.slne.dev/repository/maven-public/") { name = "maven-public" } } dependencies { - classpath("dev.slne.surf:surf-api-gradle-plugin:1.21.4+") + classpath("dev.slne.surf:surf-api-gradle-plugin:1.21.7+") } } plugins { id("org.jetbrains.kotlinx.binary-compatibility-validator") version "0.17.0" - id("io.freefair.aspectj.post-compile-weaving") version "8.13.1" +// id("io.freefair.aspectj.post-compile-weaving") version "8.13.1" java } allprojects { - apply(plugin = "java") - apply(plugin = "io.freefair.aspectj.post-compile-weaving") group = "dev.slne.surf.cloud" version = findProperty("version") as String @@ -27,12 +25,21 @@ allprojects { slnePublic() } + if (name == "surf-cloud-bom") { + return@allprojects + } + + apply(plugin = "java") +// apply(plugin = "io.freefair.aspectj.post-compile-weaving") + dependencies { - implementation(platform("org.springframework.boot:spring-boot-dependencies:3.4.4")) - implementation(platform("io.ktor:ktor-bom:3.0.3")) - implementation(platform("org.jetbrains.kotlin-wrappers:kotlin-wrappers-bom:2025.4.10")) +// implementation(platform("org.springframework.boot:spring-boot-dependencies:3.4.4")) +// implementation(platform("io.ktor:ktor-bom:3.0.3")) +// implementation(platform("org.jetbrains.kotlin-wrappers:kotlin-wrappers-bom:2025.4.10")) + + implementation(platform(project(":surf-cloud-bom"))) - compileOnly("org.springframework.boot:spring-boot-configuration-processor:3.4.3") + compileOnly("org.springframework.boot:spring-boot-configuration-processor:3.5.3") // "kapt"("org.springframework.boot:spring-boot-configuration-processor:3.4.3") } @@ -47,8 +54,6 @@ allprojects { options.tags("implNote:a:Implementation Note:") } } - - setupPublishing() } apiValidation { @@ -69,23 +74,14 @@ apiValidation { private fun TaskContainerScope.configureShadowJar() = withType { mergeServiceFiles { - setPath("META-INF") + path = "META-INF" exclude("META-INF/MANIFEST.MF") } isZip64 = true + duplicatesStrategy = DuplicatesStrategy.EXCLUDE } private fun TaskContainerScope.configureJar() = withType { duplicatesStrategy = DuplicatesStrategy.EXCLUDE -} - -private fun setupPublishing() = afterEvaluate { - if (plugins.hasPlugin(PublishingPlugin::class)) { - configure { - repositories { - slnePublic() - } - } - } } \ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index fdb38889..61240d4b 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -11,7 +11,7 @@ dependencies { // val kotlinVersion = "2.1.0" // val dokkaVersion = "2.0.0-Beta" - implementation("org.springframework.boot:org.springframework.boot.gradle.plugin:3.4.4") + implementation("org.springframework.boot:org.springframework.boot.gradle.plugin:3.5.3") // implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") // implementation("org.jetbrains.kotlin:kotlin-allopen:$kotlinVersion") // implementation("org.jetbrains.kotlin:kotlin-lombok:$kotlinVersion") diff --git a/buildSrc/src/main/kotlin/core-convention.gradle.kts b/buildSrc/src/main/kotlin/core-convention.gradle.kts index 663d9539..0991ba67 100644 --- a/buildSrc/src/main/kotlin/core-convention.gradle.kts +++ b/buildSrc/src/main/kotlin/core-convention.gradle.kts @@ -12,11 +12,11 @@ repositories { } dependencies { - implementation(platform("org.springframework.boot:spring-boot-dependencies:3.4.3")) - implementation(platform("io.ktor:ktor-bom:3.0.3")) - implementation(platform("org.jetbrains.kotlin-wrappers:kotlin-wrappers-bom:2025.2.2")) + implementation(platform("org.springframework.boot:spring-boot-dependencies:3.5.3")) + implementation(platform("io.ktor:ktor-bom:3.2.1")) + implementation(platform("org.jetbrains.kotlin-wrappers:kotlin-wrappers-bom:2025.7.8")) - compileOnly("org.springframework.boot:spring-boot-configuration-processor:3.4.3") + compileOnly("org.springframework.boot:spring-boot-configuration-processor:3.5.3") // "kapt"("org.springframework.boot:spring-boot-configuration-processor:3.4.3") } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index d7242bda..dd74dd15 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,4 +5,4 @@ kotlin.stdlib.default.dependency=false kotlin.code.style=official org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true -version=1.21.1-1.0.0-SNAPSHOT +version=1.21.7-1.0.0-SNAPSHOT diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a7579e07..99fc10a7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,20 +1,19 @@ [versions] velocity-version = "3.4.0-SNAPSHOT" aide-reflection = "1.3" -netty = "4.2.0.Final" -netty-tcnative = "2.0.70.Final" -nbt = "6.1" +netty = "4.2.2.Final" +netty-tcnative = "2.0.72.Final" datafixerupper = "8.0.16" #byte-buddy = "1.15.10" exposed = "0.61.0" maven-impl = "4.0.0-rc-2" maven-resolver = "2.0.5" -jline = "3.29.0" +jline = "3.30.4" brigadier = "1.3.10" terminalconsoleappender = "1.3.0" bson-kotlinx = "5.4.0" aspectjweaver = "1.9.22.1" -zstd-jni = "1.5.7-2" +zstd-jni = "1.5.7-4" luckperms-api = "5.4" reactive-streams = "1.0.4" ehcache = "3.10.8" @@ -34,11 +33,8 @@ jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module- aide-reflection = { module = "tech.hiddenproject:aide-reflection", version.ref = "aide-reflection" } netty-all = { module = "io.netty:netty-all", version.ref = "netty" } netty-tcnative = { module = "io.netty:netty-tcnative", version.ref = "netty-tcnative" } -nbt = { module = "com.github.Querz:NBT", version.ref = "nbt" } -spring-data-jpa = { module = "org.springframework.data:spring-data-jpa" } datafixerupper = { module = "com.mojang:datafixerupper", version.ref = "datafixerupper" } byte-buddy = { module = "net.bytebuddy:byte-buddy" } -jakarta-persistence-api = { module = "jakarta.persistence:jakarta.persistence-api" } exposed-spring-boot-starter = { module = "org.jetbrains.exposed:exposed-spring-boot-starter", version.ref = "exposed" } exposed-java-time = { module = "org.jetbrains.exposed:exposed-java-time", version.ref = "exposed" } exposed-migration = { module = "org.jetbrains.exposed:exposed-migration", version.ref = "exposed" } @@ -63,11 +59,8 @@ zstd-jni = { module = "com.github.luben:zstd-jni", version.ref = "zstd-jni" } luckperms-api = { module = "net.luckperms:api", version.ref = "luckperms-api" } mariadb-java-client = { module = "org.mariadb.jdbc:mariadb-java-client" } mysql-connector-j = { module = "com.mysql:mysql-connector-j" } -spring-boot-starter-data-jpa = { module = "org.springframework.boot:spring-boot-starter-data-jpa" } spring-boot-starter-actuator = { module = "org.springframework.boot:spring-boot-starter-actuator" } reactive-streams = { module = "org.reactivestreams:reactive-streams", version.ref = "reactive-streams" } -hibernate-jcache = { module = "org.hibernate.orm:hibernate-jcache" } -ehcache = { module = "org.ehcache:ehcache", version.ref = "ehcache" } ktor-server-status-pages = { module = "io.ktor:ktor-server-status-pages" } spring-boot-starter-log4j2 = { module = "org.springframework.boot:spring-boot-starter-log4j2" } spring-aop = { module = "org.springframework:spring-aop" } @@ -79,7 +72,6 @@ spring-instrument = { module = "org.springframework:spring-instrument" } kotlin-byte-buf-serializer = { module = "dev.slne.surf:kotlin-byte-buf-serializer", version.ref = "kotlin-byte-buf-serializer" } voicechat-api = { module = "de.maxhenkel.voicechat:voicechat-api", version.ref = "voicechat-api" } discord-webhooks = { module = "com.github.BinaryWriter:discord-webhooks", version.ref = "discord-webhooks" } -konf = { module = "com.uchuhimo:konf", version.ref = "konf" } [plugins] spring-boot = { id = "org.springframework.boot" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ca025c83..d4081da4 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.14-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle.kts b/settings.gradle.kts index 2a53e0fd..214e6d64 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -35,7 +35,8 @@ findProject(":surf-cloud-api:surf-cloud-api-client:surf-cloud-api-client-paper") include("surf-cloud-api:surf-cloud-api-client:surf-cloud-api-client-velocity") findProject(":surf-cloud-api:surf-cloud-api-client:surf-cloud-api-client-velocity")?.name = "surf-cloud-api-client-velocity" +include("surf-cloud-bom") + if (!ci) { include(":surf-cloud-test-plugin:surf-cloud-test-standalone") -} - +} \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-client/surf-cloud-api-client-common/build.gradle.kts b/surf-cloud-api/surf-cloud-api-client/surf-cloud-api-client-common/build.gradle.kts index 03725083..179a425d 100644 --- a/surf-cloud-api/surf-cloud-api-client/surf-cloud-api-client-common/build.gradle.kts +++ b/surf-cloud-api/surf-cloud-api-client/surf-cloud-api-client-common/build.gradle.kts @@ -1,7 +1,16 @@ +import dev.slne.surf.surfapi.gradle.util.slneReleases + plugins { + `exclude-kotlin` id("dev.slne.surf.surfapi.gradle.core") } dependencies { api(project(":surf-cloud-api:surf-cloud-api-common")) +} + +publishing { + repositories { + slneReleases() + } } \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-client/surf-cloud-api-client-paper/build.gradle.kts b/surf-cloud-api/surf-cloud-api-client/surf-cloud-api-client-paper/build.gradle.kts index c6effa55..a087f336 100644 --- a/surf-cloud-api/surf-cloud-api-client/surf-cloud-api-client-paper/build.gradle.kts +++ b/surf-cloud-api/surf-cloud-api-client/surf-cloud-api-client-paper/build.gradle.kts @@ -1,7 +1,16 @@ +import dev.slne.surf.surfapi.gradle.util.slneReleases + plugins { + `exclude-kotlin` id("dev.slne.surf.surfapi.gradle.paper-raw") } dependencies { api(project(":surf-cloud-api:surf-cloud-api-client:surf-cloud-api-client-common")) +} + +publishing { + repositories { + slneReleases() + } } \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-client/surf-cloud-api-client-paper/src/main/kotlin/dev/slne/surf/cloud/api/client/paper/paper-cloud-converter.kt b/surf-cloud-api/surf-cloud-api-client/surf-cloud-api-client-paper/src/main/kotlin/dev/slne/surf/cloud/api/client/paper/paper-cloud-converter.kt index acb479d5..2db7d740 100644 --- a/surf-cloud-api/surf-cloud-api-client/surf-cloud-api-client-paper/src/main/kotlin/dev/slne/surf/cloud/api/client/paper/paper-cloud-converter.kt +++ b/surf-cloud-api/surf-cloud-api-client/surf-cloud-api-client-paper/src/main/kotlin/dev/slne/surf/cloud/api/client/paper/paper-cloud-converter.kt @@ -18,10 +18,10 @@ fun PlayerTeleportEvent.TeleportCause.toCloudTpCause(): TeleportCause = when (th PlayerTeleportEvent.TeleportCause.SPECTATE -> TeleportCause.SPECTATE PlayerTeleportEvent.TeleportCause.EXIT_BED -> TeleportCause.EXIT_BED PlayerTeleportEvent.TeleportCause.END_PORTAL -> TeleportCause.END_PORTAL - PlayerTeleportEvent.TeleportCause.CHORUS_FRUIT -> TeleportCause.CHORUS_FRUIT PlayerTeleportEvent.TeleportCause.ENDER_PEARL -> TeleportCause.ENDER_PEARL PlayerTeleportEvent.TeleportCause.END_GATEWAY -> TeleportCause.END_GATEWAY PlayerTeleportEvent.TeleportCause.NETHER_PORTAL -> TeleportCause.NETHER_PORTAL + PlayerTeleportEvent.TeleportCause.CONSUMABLE_EFFECT -> TeleportCause.CONSUMABLE_EFFECT } fun TeleportCause.toBukkitTpCause(): PlayerTeleportEvent.TeleportCause = when (this) { @@ -32,10 +32,10 @@ fun TeleportCause.toBukkitTpCause(): PlayerTeleportEvent.TeleportCause = when (t TeleportCause.SPECTATE -> PlayerTeleportEvent.TeleportCause.SPECTATE TeleportCause.EXIT_BED -> PlayerTeleportEvent.TeleportCause.EXIT_BED TeleportCause.END_PORTAL -> PlayerTeleportEvent.TeleportCause.END_PORTAL - TeleportCause.CHORUS_FRUIT -> PlayerTeleportEvent.TeleportCause.CHORUS_FRUIT TeleportCause.ENDER_PEARL -> PlayerTeleportEvent.TeleportCause.ENDER_PEARL TeleportCause.END_GATEWAY -> PlayerTeleportEvent.TeleportCause.END_GATEWAY TeleportCause.NETHER_PORTAL -> PlayerTeleportEvent.TeleportCause.NETHER_PORTAL + TeleportCause.CONSUMABLE_EFFECT -> PlayerTeleportEvent.TeleportCause.CONSUMABLE_EFFECT } fun TeleportFlag.toCloudTpFlag(): CloudTeleportFlag = when (this) { diff --git a/surf-cloud-api/surf-cloud-api-client/surf-cloud-api-client-velocity/build.gradle.kts b/surf-cloud-api/surf-cloud-api-client/surf-cloud-api-client-velocity/build.gradle.kts index cdf830df..ada6341d 100644 --- a/surf-cloud-api/surf-cloud-api-client/surf-cloud-api-client-velocity/build.gradle.kts +++ b/surf-cloud-api/surf-cloud-api-client/surf-cloud-api-client-velocity/build.gradle.kts @@ -1,7 +1,16 @@ +import dev.slne.surf.surfapi.gradle.util.slneReleases + plugins { - id("dev.slne.surf.surfapi.gradle.core") + `exclude-kotlin` + id("dev.slne.surf.surfapi.gradle.velocity") } dependencies { api(project(":surf-cloud-api:surf-cloud-api-client:surf-cloud-api-client-common")) +} + +publishing { + repositories { + slneReleases() + } } \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-client/surf-cloud-api-client-velocity/src/main/kotlin/dev/slne/surf/cloud/api/client/velocity/server/VelocityCloudServeExtensions.kt b/surf-cloud-api/surf-cloud-api-client/surf-cloud-api-client-velocity/src/main/kotlin/dev/slne/surf/cloud/api/client/velocity/server/VelocityCloudServeExtensions.kt new file mode 100644 index 00000000..172a46ab --- /dev/null +++ b/surf-cloud-api/surf-cloud-api-client/surf-cloud-api-client-velocity/src/main/kotlin/dev/slne/surf/cloud/api/client/velocity/server/VelocityCloudServeExtensions.kt @@ -0,0 +1,8 @@ +package dev.slne.surf.cloud.api.client.velocity.server + +import com.velocitypowered.api.proxy.ProxyServer +import dev.slne.surf.cloud.api.common.server.CloudServer +import kotlin.jvm.optionals.getOrNull + +fun CloudServer.toRegisteredServer(proxy: ProxyServer) = + proxy.getServer(name).getOrNull() ?: error("Server $name is not registered in Velocity proxy") \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-common/.gitignore b/surf-cloud-api/surf-cloud-api-common/.gitignore new file mode 100644 index 00000000..0117975f --- /dev/null +++ b/surf-cloud-api/surf-cloud-api-common/.gitignore @@ -0,0 +1 @@ +src/main/resources/cloud.version \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-common/build.gradle.kts b/surf-cloud-api/surf-cloud-api-common/build.gradle.kts index 68d2e838..ee242ce1 100644 --- a/surf-cloud-api/surf-cloud-api-common/build.gradle.kts +++ b/surf-cloud-api/surf-cloud-api-common/build.gradle.kts @@ -1,10 +1,12 @@ +import dev.slne.surf.surfapi.gradle.util.slneReleases + plugins { + `exclude-kotlin` id("dev.slne.surf.surfapi.gradle.core") } dependencies { api(libs.bundles.spring.api.common) - implementation(libs.spring.data.jpa) // Hide this from the API api(libs.bundles.jackson.api.common) api(libs.bundles.spring.aop) @@ -14,16 +16,39 @@ dependencies { exclude(group = "io.netty") } - api(libs.nbt) api(libs.datafixerupper) { isTransitive = false } api(libs.byte.buddy) - api(libs.spring.boot.starter.actuator) +} + +val writeCloudVersion by tasks.registering { + group = "build" + description = "Writes the cloud version to the classpath resource" + + val outputDir = layout.projectDirectory.dir("src/main/resources") + val outputFile = outputDir.file("cloud.version") + + doLast { + outputDir.asFile.mkdirs() + outputFile.asFile.writeText(project.version as String) + } +} + +tasks { + processResources { + dependsOn(writeCloudVersion) + } } kotlin { compilerOptions { optIn.add("dev.slne.surf.cloud.api.common.util.annotation.InternalApi") } +} + +publishing { + repositories { + slneReleases() + } } \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/SurfCloudApplication.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/SurfCloudApplication.kt index 9e83fa12..35d75791 100644 --- a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/SurfCloudApplication.kt +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/SurfCloudApplication.kt @@ -3,12 +3,11 @@ package dev.slne.surf.cloud.api.common import org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter import org.springframework.boot.autoconfigure.AutoConfigurationPackage import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration import org.springframework.boot.autoconfigure.domain.EntityScan import org.springframework.boot.context.TypeExcludeFilter import org.springframework.cache.annotation.EnableCaching +import org.springframework.context.annotation.AdviceMode import org.springframework.context.annotation.ComponentScan -import org.springframework.context.annotation.EnableAspectJAutoProxy import org.springframework.context.annotation.FilterType import org.springframework.scheduling.annotation.EnableAsync import org.springframework.scheduling.annotation.EnableScheduling @@ -24,10 +23,10 @@ import java.lang.annotation.Inherited @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) @EnableScheduling -@EnableAsync -@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true) +@EnableAsync(mode = AdviceMode.ASPECTJ) +//@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true) @EntityScan -@EnableCaching +@EnableCaching(mode = AdviceMode.ASPECTJ) @AutoConfigurationPackage @Inherited @EnableAutoConfiguration diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/meta/SurfNettyPacket.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/meta/SurfNettyPacket.kt index c335850d..aa28faa7 100644 --- a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/meta/SurfNettyPacket.kt +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/meta/SurfNettyPacket.kt @@ -17,7 +17,7 @@ annotation class SurfNettyPacket( val id: String, val flow: PacketFlow, @property:InternalApi - vararg val protocols: ConnectionProtocol = [ConnectionProtocol.RUNNING] + vararg val protocols: ConnectionProtocol = [ConnectionProtocol.RUNNING, ConnectionProtocol.SYNCHRONIZING] ) /** @@ -43,6 +43,7 @@ object DefaultIds { // Login const val SERVERBOUND_LOGIN_START_PACKET = "cloud:serverbound:login_start" const val CLIENTBOUND_LOGIN_FINISHED_PACKET = "cloud:clientbound:login_finished" + const val SERVERBOUND_WAIT_FOR_SERVER_TO_START_PACKET = "cloud:serverbound:wait_for_server_to_start" const val SERVERBOUND_LOGIN_ACKNOWLEDGED_PACKET = "cloud:serverbound:login_acknowledged" const val CLIENTBOUND_LOGIN_DISCONNECT_PACKET = "cloud:clientbound:login_disconnect" const val SERVERBOUND_KEY_PACKET = @@ -125,6 +126,7 @@ object DefaultIds { const val CLIENTBOUND_CLEAR_RESOURCE_PACKS_PACKET = "cloud:clientbound:clear_resource_packs" const val PLAYER_CONNECT_TO_SERVER_PACKET = "cloud:player:connect_to_server" + const val PLAYER_CONNECTED_TO_SERVER_PACKET = "cloud:player:connected_to_server" const val PLAYER_DISCONNECT_FROM_SERVER_PACKET = "cloud:player:disconnect_from_server" const val SERVERBOUND_REQUEST_DISPLAY_NAME_PACKET = "cloud:serverbound:request_display_name" diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/NettyClient.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/NettyClient.kt index f9a25ce5..c243fdfa 100644 --- a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/NettyClient.kt +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/NettyClient.kt @@ -15,6 +15,8 @@ interface NettyClient { val serverCategory: String val serverName: String + val velocitySecret: ByteArray + val connection: Connection /** diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/network/Connection.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/network/Connection.kt index d7005722..01659e00 100644 --- a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/network/Connection.kt +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/network/Connection.kt @@ -13,6 +13,8 @@ interface Connection { val averageReceivedPackets: Float val averageSentPackets: Float + val latency: Int + val hostname: String val virtualHost: InetSocketAddress diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/network/ConnectionProtocol.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/network/ConnectionProtocol.kt index 0b9e51ec..123d4ce7 100644 --- a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/network/ConnectionProtocol.kt +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/network/ConnectionProtocol.kt @@ -8,6 +8,7 @@ enum class ConnectionProtocol { INITIALIZE, LOGIN, PRE_RUNNING, + SYNCHRONIZING, RUNNING, SHUTDOWN } \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/network/codec/kotlinx/SurfCloudBufSerializer.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/network/codec/kotlinx/SurfCloudBufSerializer.kt index 22a5c8eb..f6e18308 100644 --- a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/network/codec/kotlinx/SurfCloudBufSerializer.kt +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/network/codec/kotlinx/SurfCloudBufSerializer.kt @@ -9,9 +9,11 @@ import dev.slne.surf.cloud.api.common.netty.network.codec.kotlinx.cloud.OfflineC import dev.slne.surf.cloud.api.common.netty.network.codec.kotlinx.java.* import dev.slne.surf.cloud.api.common.netty.network.codec.kotlinx.kotlin.DurationSerializer import dev.slne.surf.cloud.api.common.netty.network.codec.kotlinx.nbt.CompoundTagSerializer +import dev.slne.surf.cloud.api.common.util.annotation.InternalApi import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.contextual +@InternalApi object SurfCloudBufSerializer { val serializerModule = SerializersModule { // Adventure diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/network/codec/kotlinx/nbt/CompoundTagSerializer.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/network/codec/kotlinx/nbt/CompoundTagSerializer.kt index 23c9f93c..4805f69b 100644 --- a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/network/codec/kotlinx/nbt/CompoundTagSerializer.kt +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/network/codec/kotlinx/nbt/CompoundTagSerializer.kt @@ -5,21 +5,22 @@ import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.ByteArraySerializer import kotlinx.serialization.descriptors.SerialDescriptor -import net.querz.nbt.tag.CompoundTag +import net.kyori.adventure.nbt.CompoundBinaryTag -typealias SerializableCompoundTag = @Serializable(with = CompoundTagSerializer::class) CompoundTag +typealias SerializableCompoundTag = @Serializable(with = CompoundTagSerializer::class) CompoundBinaryTag -object CompoundTagSerializer : CloudBufSerializer() { - override val descriptor = SerialDescriptor("CompoundTag", ByteArraySerializer().descriptor) +object CompoundTagSerializer : CloudBufSerializer() { + override val descriptor = + SerialDescriptor("CompoundBinaryTag", ByteArraySerializer().descriptor) override fun serialize0( buf: SurfByteBuf, - value: CompoundTag + value: CompoundBinaryTag ) { buf.writeCompoundTag(value) } - override fun deserialize0(buf: SurfByteBuf): CompoundTag { + override fun deserialize0(buf: SurfByteBuf): CompoundBinaryTag { return buf.readCompoundTag() } } \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/packet/NettyPacket.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/packet/NettyPacket.kt index a1090290..2584c5bc 100644 --- a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/packet/NettyPacket.kt +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/packet/NettyPacket.kt @@ -17,6 +17,7 @@ abstract class NettyPacket { * This is for internal use and is not intended to be modified externally. */ @InternalApi + @Transient var handled = false private set @@ -33,40 +34,48 @@ abstract class NettyPacket { * is required after this packet and the protocol switches to the next state. */ @InternalApi + @Transient open val terminal: Boolean = false // endregion + @Transient private val meta = this::class.getPacketMeta() /** * The unique identifier of this packet. */ + @Transient val id = meta.id /** * The flow direction of this packet (e.g., client-to-server or server-to-client). */ + @Transient val flow = meta.flow /** * Supported protocols for this packet. */ + @Transient val protocols = meta.protocols /** * A session identifier for the packet, generated randomly for each instance. */ + @Transient val sessionId = ThreadLocalRandom.current().nextLong() /** * Indicates whether the packet is skippable. * If true, the packet will be ignored if its size exceeds the allowed limit. */ + @Transient open val skippable = false /** * Additional packets that should be sent after this packet, if any. */ + @Transient open val extraPackets: List? = null /** diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/packet/NettyPacketInfo.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/packet/NettyPacketInfo.kt index 6dc1c3bc..188ef61e 100644 --- a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/packet/NettyPacketInfo.kt +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/packet/NettyPacketInfo.kt @@ -1,5 +1,6 @@ package dev.slne.surf.cloud.api.common.netty.packet import dev.slne.surf.cloud.api.common.netty.network.Connection +import dev.slne.surf.cloud.api.common.netty.network.ConnectionProtocol -data class NettyPacketInfo(val origin: Connection) \ No newline at end of file +data class NettyPacketInfo(val origin: Connection, val protocol: ConnectionProtocol) \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/packet/packet-extension.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/packet/packet-extension.kt index 7cf1a6f2..c0a74980 100644 --- a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/packet/packet-extension.kt +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/packet/packet-extension.kt @@ -5,12 +5,10 @@ import dev.slne.surf.cloud.api.common.meta.SurfNettyPacket import dev.slne.surf.cloud.api.common.netty.network.codec.StreamCodec import dev.slne.surf.cloud.api.common.netty.network.codec.StreamDecoder import dev.slne.surf.cloud.api.common.netty.network.codec.StreamMemberEncoder -import dev.slne.surf.cloud.api.common.netty.network.codec.kotlinx.SurfCloudBufSerializer import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf import dev.slne.surf.cloud.api.common.util.mutableObject2ObjectMapOf import io.netty.buffer.ByteBuf import kotlinx.serialization.InternalSerializationApi -import kotlinx.serialization.KSerializer import kotlinx.serialization.serializer import kotlinx.serialization.serializerOrNull import kotlin.reflect.KClass @@ -68,17 +66,8 @@ private val codecCache = mutableObject2ObjectMapOf, Stre @OptIn(InternalSerializationApi::class) -fun

KClass.createCodec(): StreamCodec { - val serializer = serializer() - return object : StreamCodec { - override fun decode(buf: SurfByteBuf): P { - return SurfCloudBufSerializer.serializer.decodeFromBuf(buf, serializer) - } - - override fun encode(buf: SurfByteBuf, value: P) { - SurfCloudBufSerializer.serializer.encodeToBuf(buf, serializer as KSerializer

, value) - } - } +fun

KClass

.createCodec(): StreamCodec { + return SurfByteBuf.streamCodecFromKotlin(serializer()) } /** @@ -96,15 +85,7 @@ fun KClass.findPacketCodec(): StreamCodec< val serializer = serializerOrNull() if (serializer != null) { - return object : StreamCodec { - override fun decode(buf: B): V { - return SurfCloudBufSerializer.serializer.decodeFromBuf(buf, serializer) - } - - override fun encode(buf: B, value: V) { - SurfCloudBufSerializer.serializer.encodeToBuf(buf, serializer as KSerializer, value) - } - } + return SurfByteBuf.streamCodecFromKotlin(serializer) } val properties = diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/protocol/buffer/SurfByteBuf.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/protocol/buffer/SurfByteBuf.kt index 52ccbecc..ea12404a 100644 --- a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/protocol/buffer/SurfByteBuf.kt +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/netty/protocol/buffer/SurfByteBuf.kt @@ -6,6 +6,8 @@ import com.google.gson.Gson import com.google.gson.JsonElement import com.mojang.serialization.Codec import com.mojang.serialization.JsonOps +import dev.slne.surf.cloud.api.common.netty.network.codec.StreamCodec +import dev.slne.surf.cloud.api.common.netty.network.codec.kotlinx.SurfCloudBufSerializer import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf.Companion import dev.slne.surf.cloud.api.common.netty.protocol.buffer.decoder.DecodeFactory import dev.slne.surf.cloud.api.common.netty.protocol.buffer.decoder.DecodeFactory.DecodeLongFactory @@ -18,14 +20,17 @@ import dev.slne.surf.cloud.api.common.util.codec.ExtraCodecs import dev.slne.surf.cloud.api.common.util.createUnresolvedInetSocketAddress import dev.slne.surf.cloud.api.common.util.fromJson import io.netty.buffer.ByteBuf +import io.netty.buffer.ByteBufAllocator +import io.netty.buffer.PooledByteBufAllocator import io.netty.handler.codec.DecoderException import io.netty.handler.codec.EncoderException import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.ObjectArrayList +import kotlinx.serialization.KSerializer import net.kyori.adventure.key.Key +import net.kyori.adventure.nbt.CompoundBinaryTag import net.kyori.adventure.sound.Sound import net.kyori.adventure.text.Component -import net.querz.nbt.tag.CompoundTag import java.io.* import java.net.Inet4Address import java.net.InetSocketAddress @@ -88,8 +93,28 @@ private const val NUMBER_DOUBLE: Byte = 5 */ open class SurfByteBuf(source: ByteBuf) : WrappedByteBuf(source) { companion object { + val alloc: ByteBufAllocator = PooledByteBufAllocator.DEFAULT + private val GSON = Gson() + fun streamCodecFromKotlin(serializer: KSerializer): StreamCodec { + return SerializerCodec(serializer) + } + + private class SerializerCodec(private val serializer: KSerializer) : + StreamCodec { + override fun decode(buf: B): T { + return SurfCloudBufSerializer.serializer.decodeFromBuf(buf, serializer) + } + + override fun encode( + buf: B, + value: T + ) { + SurfCloudBufSerializer.serializer.encodeToBuf(buf, serializer, value) + } + } + /** * Read key key. * @@ -566,7 +591,8 @@ open class SurfByteBuf(source: ByteBuf) : WrappedByteBuf(source) { buf: B, decodeFactory: DecodeLongFactory = DecodeLongFactory { buf -> buf.readLong() } - ): OptionalLong = if (buf.readBoolean()) OptionalLong.of(decodeFactory.decodeLong(buf)) else OptionalLong.empty() + ): OptionalLong = + if (buf.readBoolean()) OptionalLong.of(decodeFactory.decodeLong(buf)) else OptionalLong.empty() @Deprecated("Use codec instead") @@ -626,18 +652,18 @@ open class SurfByteBuf(source: ByteBuf) : WrappedByteBuf(source) { fun readInetSocketAddress(buf: B) = createUnresolvedInetSocketAddress(readUtf(buf), buf.readVarInt()) - fun writeCompoundTag(buf: B, tag: CompoundTag) { + fun writeCompoundTag(buf: B, tag: CompoundBinaryTag) { ExtraCodecs.COMPOUND_TAG_CODEC.encode(buf, tag) } fun readCompoundTag(buf: B) = ExtraCodecs.COMPOUND_TAG_CODEC.decode(buf) - fun writeZonedDateTime(buf: B, time: ZonedDateTime) { + fun writeZonedDateTime(buf: B, time: ZonedDateTime) { buf.writeLong(time.toInstant().toEpochMilli()) writeUtf(buf, time.zone.id) } - fun readZonedDateTime(buf: B): ZonedDateTime { + fun readZonedDateTime(buf: B): ZonedDateTime { val epoch = buf.readLong() val zone = readUtf(buf) return ZonedDateTime.ofInstant(Instant.ofEpochMilli(epoch), ZoneId.of(zone)) @@ -663,11 +689,11 @@ open class SurfByteBuf(source: ByteBuf) : WrappedByteBuf(source) { ?: throw DecoderException("Failed to read singleton: $className") } - fun writeDuration(buf: B, duration: Duration) { + fun writeDuration(buf: B, duration: Duration) { buf.writeLong(duration.inWholeMilliseconds) } - fun readDuration(buf: B): Duration { + fun readDuration(buf: B): Duration { return buf.readLong().milliseconds } } @@ -837,7 +863,7 @@ open class SurfByteBuf(source: ByteBuf) : WrappedByteBuf(source) { fun readWithCount(reader: Consumer) = readWithCount(this, reader) fun writeInetSocketAddress(address: InetSocketAddress) = writeInetSocketAddress(this, address) fun readInetSocketAddress() = readInetSocketAddress(this) - fun writeCompoundTag(tag: CompoundTag) = writeCompoundTag(this, tag) + fun writeCompoundTag(tag: CompoundBinaryTag) = writeCompoundTag(this, tag) fun readCompoundTag() = readCompoundTag(this) fun writeZonedDateTime(time: ZonedDateTime) = writeZonedDateTime(this, time) fun readZonedDateTime() = readZonedDateTime(this) @@ -1068,15 +1094,19 @@ fun B.writeInetSocketAddress(address: InetSocketAddress) = SurfByteBuf.writeInetSocketAddress(this, address) fun B.readCompoundTag() = SurfByteBuf.readCompoundTag(this) -fun B.writeCompoundTag(tag: CompoundTag) = SurfByteBuf.writeCompoundTag(this, tag) +fun B.writeCompoundTag(tag: CompoundBinaryTag) = SurfByteBuf.writeCompoundTag(this, tag) fun B.readZonedDateTime() = SurfByteBuf.readZonedDateTime(this) -fun B.writeZonedDateTime(time: ZonedDateTime) = SurfByteBuf.writeZonedDateTime(this, time) +fun B.writeZonedDateTime(time: ZonedDateTime) = + SurfByteBuf.writeZonedDateTime(this, time) fun B.readInet4Address() = SurfByteBuf.readInet4Address(this) -fun B.writeInet4Address(address: Inet4Address) = SurfByteBuf.writeInet4Address(this, address) +fun B.writeInet4Address(address: Inet4Address) = + SurfByteBuf.writeInet4Address(this, address) + +fun B.readSingleton(classLoader: ClassLoader) = + SurfByteBuf.readSingleton(this, classLoader) -fun B.readSingleton(classLoader: ClassLoader) = SurfByteBuf.readSingleton(this, classLoader) fun B.writeSingleton(singleton: Any) = SurfByteBuf.writeSingleton(this, singleton) fun B.writeDuration(duration: Duration) = SurfByteBuf.writeDuration(this, duration) diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/player/CloudPlayer.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/player/CloudPlayer.kt index 8e1f8437..150337d0 100644 --- a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/player/CloudPlayer.kt +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/player/CloudPlayer.kt @@ -33,6 +33,8 @@ interface CloudPlayer : Audience, OfflineCloudPlayer { // TODO: conversation but override suspend fun latestIpAddress(): Inet4Address override suspend fun lastServerRaw(): String + override suspend fun lastServer(): CloudServer = currentServer() + fun currentServer(): CloudServer /** * Whether the player is currently connected to a proxy server. diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/task/PrePlayerJoinTask.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/player/task/PrePlayerJoinTask.kt similarity index 64% rename from surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/task/PrePlayerJoinTask.kt rename to surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/player/task/PrePlayerJoinTask.kt index 4a6a63d0..97e8219f 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/task/PrePlayerJoinTask.kt +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/player/task/PrePlayerJoinTask.kt @@ -1,12 +1,13 @@ -package dev.slne.surf.cloud.core.common.player.task +package dev.slne.surf.cloud.api.common.player.task -import dev.slne.surf.cloud.core.common.player.CommonOfflineCloudPlayerImpl +import dev.slne.surf.cloud.api.common.player.OfflineCloudPlayer +import dev.slne.surf.cloud.api.common.util.annotation.InternalApi import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import net.kyori.adventure.text.Component -interface PrePlayerJoinTask { // TODO: 19.04.2025 12:47 - implement - suspend fun preJoin(player: CommonOfflineCloudPlayerImpl): Result +interface PrePlayerJoinTask { + suspend fun preJoin(player: OfflineCloudPlayer): Result @Serializable sealed interface Result { @@ -21,8 +22,10 @@ interface PrePlayerJoinTask { // TODO: 19.04.2025 12:47 - implement data object ERROR: Result } + @InternalApi companion object { const val PUNISHMENT_MANAGER = 500 + const val VELOCITY_PLAYER_JOIN_VALIDATION = 600 const val MUTE_PUNISHMENT_LISTENER = 750 const val ATTACH_IP_ADDRESS_HANDLER = 900 const val PUNISHMENT_LOGIN_VALIDATION_HANDLER = 1000 diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/player/teleport/TeleportCause.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/player/teleport/TeleportCause.kt index 138399c8..5e5bd672 100644 --- a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/player/teleport/TeleportCause.kt +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/player/teleport/TeleportCause.kt @@ -42,12 +42,6 @@ enum class TeleportCause { */ END_GATEWAY, - /** - * Indicates the teleportation was caused by a player consuming chorus - * fruit - */ - CHORUS_FRUIT, - /** * Indicates the teleportation was caused by a player exiting a vehicle */ @@ -62,5 +56,7 @@ enum class TeleportCause { * Indicates the teleportation was caused by an event not covered by * this enum */ - UNKNOWN + UNKNOWN, + + CONSUMABLE_EFFECT } \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/plugin/spring/task/CloudInitialSynchronizeTask.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/plugin/spring/task/CloudInitialSynchronizeTask.kt new file mode 100644 index 00000000..a7b8ff3a --- /dev/null +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/plugin/spring/task/CloudInitialSynchronizeTask.kt @@ -0,0 +1,12 @@ +package dev.slne.surf.cloud.api.common.plugin.spring.task + +import dev.slne.surf.cloud.api.common.netty.NettyClient +import org.jetbrains.annotations.ApiStatus + +interface CloudInitialSynchronizeTask { + val name: String + get() = this::class.simpleName ?: "Unknown" + + @ApiStatus.OverrideOnly + suspend fun execute(client: NettyClient) +} \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/server/CloudServer.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/server/CloudServer.kt index b0198a74..6e9a2974 100644 --- a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/server/CloudServer.kt +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/server/CloudServer.kt @@ -3,7 +3,6 @@ package dev.slne.surf.cloud.api.common.server import dev.slne.surf.cloud.api.common.player.CloudPlayer import dev.slne.surf.cloud.api.common.player.ConnectionResultEnum import it.unimi.dsi.fastutil.objects.ObjectList -import net.kyori.adventure.text.Component import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.Unmodifiable @@ -25,5 +24,7 @@ interface CloudServer : CommonCloudServer { */ val allowlist: Boolean + val lobby: Boolean + suspend fun pullPlayers(players: Collection): @Unmodifiable ObjectList> } \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/server/CloudServerManager.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/server/CloudServerManager.kt index 24ce81b4..6ca242d9 100644 --- a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/server/CloudServerManager.kt +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/server/CloudServerManager.kt @@ -70,6 +70,9 @@ interface CloudServerManager { suspend fun retrieveAllServers(): ObjectCollection + suspend fun retrieveServers(): ObjectCollection + suspend fun retrieveProxies(): ObjectCollection + suspend fun pullPlayersToGroup( group: String, players: Collection diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/server/CommonCloudServer.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/server/CommonCloudServer.kt index bd5c6c27..21bcac5e 100644 --- a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/server/CommonCloudServer.kt +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/server/CommonCloudServer.kt @@ -7,6 +7,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectMap import net.kyori.adventure.audience.ForwardingAudience import net.kyori.adventure.text.Component import org.jetbrains.annotations.ApiStatus +import java.net.InetSocketAddress /** * Represents the result of a batch transfer operation. @@ -73,6 +74,8 @@ interface CommonCloudServer : ForwardingAudience { */ val users: UserList + val playAddress: InetSocketAddress + val displayName: String get() = "$group/$uid $name" diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/sync/SyncRegistry.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/sync/SyncRegistry.kt new file mode 100644 index 00000000..318c7ea4 --- /dev/null +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/sync/SyncRegistry.kt @@ -0,0 +1,26 @@ +package dev.slne.surf.cloud.api.common.sync + +import dev.slne.surf.cloud.api.common.netty.network.codec.StreamCodec +import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf +import dev.slne.surf.cloud.api.common.util.annotation.InternalApi +import dev.slne.surf.surfapi.core.api.util.requiredService + +@InternalApi +interface SyncRegistry { + + fun createSyncValue( + id: String, + defaultValue: T, + codec: StreamCodec + ): SyncValue + + fun createSyncSet( + id: String, + codec: StreamCodec + ): SyncSet + + companion object { + @InternalApi + val instance = requiredService() + } +} \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/sync/SyncSet.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/sync/SyncSet.kt new file mode 100644 index 00000000..492208d2 --- /dev/null +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/sync/SyncSet.kt @@ -0,0 +1,53 @@ +package dev.slne.surf.cloud.api.common.sync + +import dev.slne.surf.cloud.api.common.netty.network.codec.StreamCodec +import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf +import it.unimi.dsi.fastutil.objects.ObjectSet +import kotlinx.serialization.KSerializer +import kotlinx.serialization.serializer +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.UnmodifiableView + +typealias SyncSetListener = (added: Boolean, element: T) -> Unit + +interface SyncSet : ObjectSet { + val id: String + + /** + * There is generally no need to use this codec directly, as this sync set should always + * be automatically synchronized across the network. + */ + @get:ApiStatus.Obsolete + val codec: StreamCodec> + + fun subscribe(listener: SyncSetListener): Boolean + fun snapshot(): @UnmodifiableView ObjectSet + + companion object { + operator fun invoke( + id: String, + codec: StreamCodec + ): SyncSet = of(id, codec) + + inline operator fun invoke(id: String): SyncSet = serializable(id) + operator fun invoke( + id: String, + serializer: KSerializer + ): SyncSet = serializable(id, serializer) + + inline fun serializable( + id: String, + ): SyncSet = serializable(id, serializer()) + + fun serializable( + id: String, + serializer: KSerializer, + ): SyncSet = of( + id, + SurfByteBuf.streamCodecFromKotlin(serializer) + ) + + fun of(id: String, codec: StreamCodec): SyncSet = + SyncRegistry.instance.createSyncSet(id, codec) + } +} \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/sync/SyncValue.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/sync/SyncValue.kt new file mode 100644 index 00000000..80ab9f2d --- /dev/null +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/sync/SyncValue.kt @@ -0,0 +1,70 @@ +package dev.slne.surf.cloud.api.common.sync + +import dev.slne.surf.cloud.api.common.netty.network.codec.StreamCodec +import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf +import kotlinx.serialization.KSerializer +import kotlinx.serialization.serializer +import kotlin.reflect.KProperty +import kotlin.time.Duration + +typealias SyncValueListener = (old: T, new: T) -> Unit + +interface SyncValue { + + val id: String + + val codec: StreamCodec + + /** + * Returns the current value of this sync value. + */ + fun get(): T + + /** + * Sets a new value for this sync value. + */ + fun set(newValue: T) + + /** + * Subscribes to changes in this sync value. + */ + fun subscribe(listener: SyncValueListener): Boolean + + operator fun getValue(thisRef: Any?, property: KProperty<*>): T = get() + operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T) = set(newValue) + + fun rateLimited(minInterval: Duration): SyncValue + + companion object { + operator fun invoke( + id: String, + defaultValue: T, + codec: StreamCodec + ): SyncValue = of(id, defaultValue, codec) + + inline operator fun invoke(id: String, defaultValue: T): SyncValue = serializable(id, defaultValue) + operator fun invoke( + id: String, + defaultValue: T, + serializer: KSerializer + ): SyncValue = serializable(id, defaultValue, serializer) + + inline fun serializable( + id: String, + defaultValue: T + ): SyncValue = serializable(id, defaultValue, serializer()) + + fun serializable( + id: String, + defaultValue: T, + serializer: KSerializer, + ): SyncValue = of( + id, + defaultValue, + SurfByteBuf.streamCodecFromKotlin(serializer) + ) + + fun of(id: String, defaultValue: T, codec: StreamCodec): SyncValue = + SyncRegistry.instance.createSyncValue(id, defaultValue, codec) + } +} \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/annotation/internal-api-annotations.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/annotation/internal-api-annotations.kt index aabaa066..e918ac74 100644 --- a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/annotation/internal-api-annotations.kt +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/annotation/internal-api-annotations.kt @@ -4,5 +4,4 @@ package dev.slne.surf.cloud.api.common.util.annotation "This API is internal and may change without notice.", RequiresOptIn.Level.ERROR, ) - annotation class InternalApi \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/codec/codec-util.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/codec/codec-util.kt index 9cf6c795..8c474cb1 100644 --- a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/codec/codec-util.kt +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/codec/codec-util.kt @@ -17,6 +17,8 @@ import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import net.kyori.adventure.bossbar.BossBar import net.kyori.adventure.inventory.Book import net.kyori.adventure.key.Key +import net.kyori.adventure.nbt.BinaryTagIO +import net.kyori.adventure.nbt.CompoundBinaryTag import net.kyori.adventure.resource.ResourcePackInfo import net.kyori.adventure.resource.ResourcePackRequest import net.kyori.adventure.sound.Sound @@ -26,12 +28,6 @@ import net.kyori.adventure.text.TextComponent import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer import net.kyori.adventure.title.Title import net.kyori.adventure.title.TitlePart -import net.querz.nbt.io.NBTDeserializer -import net.querz.nbt.io.NBTInputStream -import net.querz.nbt.io.NBTOutputStream -import net.querz.nbt.io.NBTSerializer -import net.querz.nbt.io.NamedTag -import net.querz.nbt.tag.CompoundTag import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.time.Duration @@ -385,11 +381,16 @@ object ExtraCodecs { // endregion // region nbt - val COMPOUND_TAG_CODEC = streamCodec({ buf, tag -> - val bytes = NBTSerializer(true).toBytes(NamedTag("", tag)) - buf.writeByteArray(bytes) + val COMPOUND_TAG_CODEC = streamCodec({ buf, tag -> + ByteArrayOutputStream().use {out -> + BinaryTagIO.writer().write(tag, out, BinaryTagIO.Compression.GZIP) + buf.writeByteArray(out.toByteArray()) + } }, { buf -> - NBTDeserializer(true).fromBytes(buf.readByteArray()).tag as CompoundTag + val bytes = buf.readByteArray() + ByteArrayInputStream(bytes).use { input -> + BinaryTagIO.unlimitedReader().read(input, BinaryTagIO.Compression.GZIP) + } }) // endregion diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/nbt/FastNbtIo.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/nbt/FastNbtIo.kt index e7789cb8..6032f9ae 100644 --- a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/nbt/FastNbtIo.kt +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/nbt/FastNbtIo.kt @@ -2,9 +2,8 @@ package dev.slne.surf.cloud.api.common.util.nbt import it.unimi.dsi.fastutil.io.FastBufferedInputStream import it.unimi.dsi.fastutil.io.FastBufferedOutputStream -import net.querz.nbt.io.NBTInputStream -import net.querz.nbt.io.NBTOutputStream -import net.querz.nbt.tag.CompoundTag +import net.kyori.adventure.nbt.BinaryTagIO +import net.kyori.adventure.nbt.CompoundBinaryTag import java.io.InputStream import java.io.OutputStream import java.nio.file.Files @@ -22,7 +21,7 @@ object FastNbtIo { StandardOpenOption.TRUNCATE_EXISTING ) - fun writeCompressed(nbt: CompoundTag, path: Path) { + fun writeCompressed(nbt: CompoundBinaryTag, path: Path) { Files.newOutputStream(path, *outputOptions).use { output -> FastBufferedOutputStream(output).use { fastOutput -> writeCompressed(nbt, fastOutput) @@ -30,34 +29,29 @@ object FastNbtIo { } } - fun writeCompressed(nbt: CompoundTag, out: OutputStream) { + fun writeCompressed(nbt: CompoundBinaryTag, out: OutputStream) { createCompressorStream(out).use { output -> writeCompoundTag(output, nbt) } } - fun readCompressed(path: Path, maxDepth: Int) = Files.newInputStream(path).use { input -> + fun readCompressed(path: Path) = Files.newInputStream(path).use { input -> FastBufferedInputStream(input).use { fastInput -> - readCompressed(fastInput, maxDepth) + readCompressed(fastInput) } } - fun readCompressed(stream: InputStream, maxDepth: Int) = + fun readCompressed(stream: InputStream) = createDecompressorStream(stream).use { input -> - readCompoundTag(input, maxDepth) + readCompoundTag(input) } - fun readCompoundTag(read: InputStream, maxDepth: Int) = - NBTInputStream(read).use { input -> - input.readTag(maxDepth).tag as? CompoundTag ?: error("Root tag is not a CompoundTag") - } + fun readCompoundTag(read: InputStream) = BinaryTagIO.unlimitedReader().read(read) - fun writeCompoundTag(out: OutputStream, tag: CompoundTag) { - NBTOutputStream(out).use { output -> - output.writeTag(tag, Int.MAX_VALUE) - } + fun writeCompoundTag(out: OutputStream, tag: CompoundBinaryTag) { + BinaryTagIO.writer().write(tag, out) } private fun createDecompressorStream(stream: InputStream) = diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/nbt/nbt-extensions.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/nbt/nbt-extensions.kt deleted file mode 100644 index 5488589c..00000000 --- a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/nbt/nbt-extensions.kt +++ /dev/null @@ -1,60 +0,0 @@ -package dev.slne.surf.cloud.api.common.util.nbt - -import net.querz.nbt.io.NBTDeserializer -import net.querz.nbt.io.NBTSerializer -import net.querz.nbt.io.NamedTag -import net.querz.nbt.tag.CompoundTag -import net.querz.nbt.tag.ListTag -import net.querz.nbt.tag.Tag -import java.nio.file.Path -import kotlin.io.path.inputStream -import kotlin.io.path.outputStream - -/** - * Extension function to put a tag into a compound tag. - * - * @param key the key to put the tag under - * @param value the tag to put - * @return the previous tag under the key, if any - * @see CompoundTag.put - */ -operator fun CompoundTag.set(key: String, value: Tag<*>): Tag<*>? = put(key, value) - -operator fun CompoundTag.set(key: String, value: Boolean): Tag<*>? = putBoolean(key, value) -operator fun CompoundTag.set(key: String, value: Byte): Tag<*>? = putByte(key, value) -operator fun CompoundTag.set(key: String, value: Short): Tag<*>? = putShort(key, value) -operator fun CompoundTag.set(key: String, value: Int): Tag<*>? = putInt(key, value) -operator fun CompoundTag.set(key: String, value: Long): Tag<*>? = putLong(key, value) -operator fun CompoundTag.set(key: String, value: Float): Tag<*>? = putFloat(key, value) -operator fun CompoundTag.set(key: String, value: Double): Tag<*>? = putDouble(key, value) -operator fun CompoundTag.set(key: String, value: String): Tag<*>? = putString(key, value) -operator fun CompoundTag.set(key: String, value: ByteArray): Tag<*>? = putByteArray(key, value) -operator fun CompoundTag.set(key: String, value: IntArray): Tag<*>? = putIntArray(key, value) -operator fun CompoundTag.set(key: String, value: LongArray): Tag<*>? = putLongArray(key, value) - - -fun ListTag<*>.getCompound(index: Int): CompoundTag { - if (index >= 0 && index < size()) { - val tag = get(index) - if (tag.id == CompoundTag.ID) { - return tag as CompoundTag - } - } - - return CompoundTag() -} - -fun Tag<*>.writeToPath(path: Path, compressed: Boolean = true) { - val tag = NamedTag(null, this) - tag.writeToPath(path, compressed) -} - -fun NamedTag.writeToPath(path: Path, compressed: Boolean = true) { - path.outputStream().use { stream -> NBTSerializer(compressed).toStream(this, stream) } -} - -fun Path.readTag(compressed: Boolean = true): NamedTag = - inputStream().use { stream -> NBTDeserializer(compressed).fromStream(stream) } - -fun Path.readCompoundTag(compressed: Boolean = true): CompoundTag = - readTag(compressed).tag as? CompoundTag ?: error("Expected a compound tag") diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/ObservableField.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/observer/ObservableField.kt similarity index 87% rename from surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/ObservableField.kt rename to surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/observer/ObservableField.kt index 629cc545..30ae642a 100644 --- a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/ObservableField.kt +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/observer/ObservableField.kt @@ -1,9 +1,11 @@ -package dev.slne.surf.cloud.api.common.util +package dev.slne.surf.cloud.api.common.util.observer +import dev.slne.surf.cloud.api.common.util.threadFactory import dev.slne.surf.surfapi.core.api.util.logger import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import org.jetbrains.annotations.ApiStatus +import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.Executors import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @@ -15,7 +17,7 @@ import kotlin.time.Duration.Companion.seconds * @property getter A function to retrieve the current value. * @property cachedValue The initial cached value of the observed field, defaults to the value returned by [getter]. * @property interval The time interval between checks for changes, defaults to 1 second. - * @property customDispatcher An optional [CoroutineDispatcher] to use for scheduling tasks. + * @property customDispatcher An optional [kotlinx.coroutines.CoroutineDispatcher] to use for scheduling tasks. */ class ObservableField( private val getter: () -> T, @@ -23,8 +25,8 @@ class ObservableField( private val interval: Duration = 1.seconds, customDispatcher: CoroutineDispatcher? = null ) { - private val channel = Channel(Channel.CONFLATED) - private val listener = mutableObjectSetOf<(T) -> Unit>() + private val channel = Channel(Channel.Factory.CONFLATED) + private val listener = CopyOnWriteArrayList<(T) -> Unit>() init { val dispatcher = customDispatcher?.let { CoroutineScope(it + SupervisorJob()) } @@ -71,6 +73,10 @@ class ObservableField( this.listener.add(listener) } + fun cachedValue(): T { + return cachedValue + } + /** * A singleton object that provides a shared [CoroutineScope] for [ObservableField] instances. * This scope is backed by a cached thread pool and includes exception handling for uncaught exceptions. @@ -95,4 +101,4 @@ class ObservableField( override val coroutineContext = dispatcher + CoroutineName("observable-field") + SupervisorJob() } -} +} \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/observer/ObservableFlow.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/observer/ObservableFlow.kt new file mode 100644 index 00000000..165bb715 --- /dev/null +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/observer/ObservableFlow.kt @@ -0,0 +1,19 @@ +package dev.slne.surf.cloud.api.common.util.observer + +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.isActive +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds + +fun observingFlow(getter: () -> T, interval: Duration = 1.seconds): Flow = flow { + emit(getter()) + while (currentCoroutineContext().isActive) { + delay(interval) + val current = getter() + emit(current) + } +}.distinctUntilChanged() \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/spring-util.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/spring-util.kt index 33309b48..30c9636d 100644 --- a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/spring-util.kt +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/spring-util.kt @@ -1,9 +1,5 @@ package dev.slne.surf.cloud.api.common.util -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.newSingleThreadContext -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withContext import org.springframework.aop.framework.AopProxyUtils import org.springframework.beans.factory.ObjectFactory import org.springframework.beans.factory.ObjectProvider @@ -11,23 +7,19 @@ import org.springframework.core.MethodIntrospector import org.springframework.core.annotation.AnnotatedElementUtils import org.springframework.core.annotation.AnnotationUtils import org.springframework.core.type.AnnotationMetadata -import org.springframework.stereotype.Component -import org.springframework.transaction.TransactionStatus -import org.springframework.transaction.support.TransactionOperations import org.springframework.util.StopWatch import java.lang.reflect.Method -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.contract -import kotlin.coroutines.CoroutineContext import kotlin.reflect.KClass operator fun ObjectFactory.getValue(thisRef: Any?, property: Any?): T = this.getObject() -operator fun ObjectFactory.getValue(thisRef: Any?, property: Any?, value: T) = this.getObject() +operator fun ObjectFactory.getValue(thisRef: Any?, property: Any?, value: T) = + this.getObject() fun Any.ultimateTargetClass() = AopProxyUtils.ultimateTargetClass(this).kotlin inline fun KClass<*>.isCandidateFor() = AnnotationUtils.isCandidateClass(this.java, A::class.java) -inline fun KClass<*>.containsMethodWithAnnotation() = + +inline fun KClass<*>.containsMethodWithAnnotation() = selectFunctions { it.isAnnotated() }.isNotEmpty() fun KClass<*>.selectFunctions(predicate: (Method) -> Boolean): MutableSet = @@ -39,7 +31,11 @@ inline fun Method.isAnnotated() = inline fun KClass<*>.findAnnotation(): A? = AnnotationUtils.findAnnotation(this.java, A::class.java) -fun AnnotationMetadata.getFieldValue(fieldName: String, expectedType: KClass<*>, classLoader: ClassLoader): Any? { +fun AnnotationMetadata.getFieldValue( + fieldName: String, + expectedType: KClass<*>, + classLoader: ClassLoader +): Any? { return try { val clazz = classLoader.loadClass(className) val field = clazz.getDeclaredField(fieldName) @@ -67,59 +63,6 @@ inline fun StopWatch.measure(taskName: String, block: () -> R): R { return result } -/** - * Default dispatcher used for coroutine-based transactional execution. - * Limits the number of concurrent transactional operations to 8, - * to avoid exhausting the database connection pool. - */ -@PublishedApi -internal val txDispatcher = newSingleThreadContext("txDispatcher").limitedParallelism(1) - -/** - * Coroutine-compatible transactional execution using [TransactionOperations]. - * - * This function allows executing a transactional [block] within a coroutine, - * using a coroutine context that is safe and optimized for database operations. - * - * The default [context] is a custom limited-parallelism dispatcher (max 8 concurrent operations), - * which prevents excessive parallel access to the database, avoiding problems like: - * - connection pool exhaustion (e.g., in HikariCP) - * - too many open EntityManager instances - * - heavy database load due to uncontrolled concurrency - * - * If needed, you can override the [context], e.g., with [Dispatchers.Default] or [Dispatchers.IO]. - * - * ### Example: - * ``` - * val result = transactionTemplate.executeAndAwait { - * myRepository.save(...) - * "Done" - * } - * ``` - * - * @param context Optional coroutine context; defaults to [txDispatcher], limiting DB concurrency. - * @param block The transactional code block to run. - * @return The result of the block. - */ -@OptIn(ExperimentalContracts::class) -@Suppress("WRONG_INVOCATION_KIND", "LEAKED_IN_PLACE_LAMBDA") -suspend inline fun TransactionOperations.executeAndAwait( - context: CoroutineContext = txDispatcher, - crossinline block: suspend (TransactionStatus) -> T -): T { - contract { - callsInPlace(block, kotlin.contracts.InvocationKind.EXACTLY_ONCE) - } - - return withContext(context) { - execute{ - runBlocking { - block(it) - } - } as T - } -} - inline fun ObjectProvider.forEachOrdered(action: (T) -> Unit) { orderedStream().iterator().forEach(action) diff --git a/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/version/CloudVersion.kt b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/version/CloudVersion.kt new file mode 100644 index 00000000..55293611 --- /dev/null +++ b/surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/version/CloudVersion.kt @@ -0,0 +1,55 @@ +package dev.slne.surf.cloud.api.common.version + +import org.springframework.core.io.ClassPathResource + +private val rawVersion by lazy { ClassPathResource("cloud.version").getContentAsString(Charsets.UTF_8) } + +object CloudVersion { + val fullVersion: String by lazy { + rawVersion + } + + val minecraftVersion: String by lazy { + rawVersion.split("-").getOrNull(0) ?: "unknown" + } + + val snapshot: Boolean by lazy { + rawVersion.endsWith("-SNAPSHOT") + } + + val version: String by lazy { + rawVersion.split("-").getOrNull(1) ?: "unknown" + } + + val major: Int by lazy { + version.split(".").getOrNull(0)?.toIntOrNull() ?: 0 + } + + val minor: Int by lazy { + version.split(".").getOrNull(1)?.toIntOrNull() ?: 0 + } + + val patch: Int by lazy { + version.split(".").getOrNull(2)?.toIntOrNull() ?: 0 + } + + fun isAtLeast(major: Int, minor: Int = 0, patch: Int = 0): Boolean { + return this.major > major || (this.major == major && this.minor > minor) || (this.major == major && this.minor == minor && this.patch >= patch) + } + + fun isAtMost(major: Int, minor: Int = 0, patch: Int = 0): Boolean { + return this.major < major || (this.major == major && this.minor < minor) || (this.major == major && this.minor == minor && this.patch <= patch) + } + + fun isEqualTo(major: Int, minor: Int = 0, patch: Int = 0): Boolean { + return this.major == major && this.minor == minor && this.patch == patch + } + + fun isNewerThan(major: Int, minor: Int = 0, patch: Int = 0): Boolean { + return this.major < major || (this.major == major && this.minor < minor) || (this.major == major && this.minor == minor && this.patch < patch) + } + + fun isOlderThan(major: Int, minor: Int = 0, patch: Int = 0): Boolean { + return this.major > major || (this.major == major && this.minor > minor) || (this.major == major && this.minor == minor && this.patch > patch) + } +} \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-server/build.gradle.kts b/surf-cloud-api/surf-cloud-api-server/build.gradle.kts index 3ada0887..39abe196 100644 --- a/surf-cloud-api/surf-cloud-api-server/build.gradle.kts +++ b/surf-cloud-api/surf-cloud-api-server/build.gradle.kts @@ -1,18 +1,20 @@ +import dev.slne.surf.surfapi.gradle.util.slneReleases + plugins { + `exclude-kotlin` id("dev.slne.surf.surfapi.gradle.core") } dependencies { api(project(":surf-cloud-api:surf-cloud-api-common")) - api(libs.jakarta.persistence.api) - api(libs.spring.data.jpa) api(libs.bundles.flyway) api(libs.bundles.exposed.api.server) api(libs.bundles.maven.libraries) api(libs.bundles.console.api) api(libs.bundles.ktor.api.server) api(libs.bson.kotlinx) + api(libs.spring.boot.starter.actuator) // api(libs.discord.webhooks) { isTransitive = true } } @@ -20,4 +22,10 @@ kotlin { compilerOptions { optIn.add("dev.slne.surf.cloud.api.common.util.annotation.InternalApi") } +} + +publishing { + repositories { + slneReleases() + } } \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-server/src/main/java/dev/slne/surf/cloud/api/server/jpa/SurfAuditableEntity.java b/surf-cloud-api/surf-cloud-api-server/src/main/java/dev/slne/surf/cloud/api/server/jpa/SurfAuditableEntity.java deleted file mode 100644 index 27c5ea9c..00000000 --- a/surf-cloud-api/surf-cloud-api-server/src/main/java/dev/slne/surf/cloud/api/server/jpa/SurfAuditableEntity.java +++ /dev/null @@ -1,38 +0,0 @@ -package dev.slne.surf.cloud.api.server.jpa; - -import jakarta.persistence.Column; -import jakarta.persistence.EntityListeners; -import jakarta.persistence.MappedSuperclass; -import java.time.ZonedDateTime; -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.annotation.LastModifiedDate; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; - -@MappedSuperclass -@EntityListeners(AuditingEntityListener.class) -public abstract class SurfAuditableEntity extends SurfEntity { - - @CreatedDate - @Column(name = "created_date") - private ZonedDateTime createdDate; - - @LastModifiedDate - @Column(name = "last_modified_date") - private ZonedDateTime lastModifiedDate; - - public ZonedDateTime getLastModifiedDate() { - return lastModifiedDate; - } - - public void setLastModifiedDate(ZonedDateTime lastModifiedDate) { - this.lastModifiedDate = lastModifiedDate; - } - - public ZonedDateTime getCreatedDate() { - return createdDate; - } - - public void setCreatedDate(ZonedDateTime createdDate) { - this.createdDate = createdDate; - } -} diff --git a/surf-cloud-api/surf-cloud-api-server/src/main/java/dev/slne/surf/cloud/api/server/jpa/SurfEntity.java b/surf-cloud-api/surf-cloud-api-server/src/main/java/dev/slne/surf/cloud/api/server/jpa/SurfEntity.java deleted file mode 100644 index 4ecafb25..00000000 --- a/surf-cloud-api/surf-cloud-api-server/src/main/java/dev/slne/surf/cloud/api/server/jpa/SurfEntity.java +++ /dev/null @@ -1,62 +0,0 @@ -package dev.slne.surf.cloud.api.server.jpa; - -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.MappedSuperclass; -import jakarta.persistence.Transient; -import java.util.Objects; -import org.springframework.data.domain.Persistable; -import org.springframework.data.util.ProxyUtils; -import org.springframework.lang.Nullable; - -/** - * Base class for JPA entities in the Surf Cloud application. Implements the {@link Persistable} - * interface to provide consistent behavior for entity lifecycle operations. - */ -@MappedSuperclass -public abstract class SurfEntity implements Persistable { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Nullable - private Long id; - - @Nullable - @Override - public Long getId() { - return id; - } - - public void setId(@Nullable Long id) { - this.id = id; - } - - @Transient - @Override - public boolean isNew() { - return null == getId(); - } - - @Override - public String toString() { - return String.format("Entity of type %s with id: %s", this.getClass().getName(), getId()); - } - - @Override - public final boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || ProxyUtils.getUserClass(this) != ProxyUtils.getUserClass(o)) { - return false; - } - SurfEntity that = (SurfEntity) o; - return getId() != null && Objects.equals(getId(), that.getId()); - } - - @Override - public final int hashCode() { - return ProxyUtils.getUserClass(this).hashCode(); - } -} diff --git a/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/command/ConsoleCommand.kt b/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/command/ConsoleCommand.kt index e9ed23a4..0fff0208 100644 --- a/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/command/ConsoleCommand.kt +++ b/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/command/ConsoleCommand.kt @@ -1,10 +1,30 @@ package dev.slne.surf.cloud.api.server.command +import com.mojang.brigadier.arguments.ArgumentType +import com.mojang.brigadier.builder.ArgumentBuilder import com.mojang.brigadier.builder.LiteralArgumentBuilder +import com.mojang.brigadier.builder.RequiredArgumentBuilder interface ConsoleCommand inline fun ConsoleCommand.literal( literal: String, block: LiteralArgumentBuilder.() -> Unit = {} -): LiteralArgumentBuilder = LiteralArgumentBuilder.literal(literal).apply(block) \ No newline at end of file +): LiteralArgumentBuilder = LiteralArgumentBuilder.literal(literal).apply(block) + +inline fun ConsoleCommand.argument( + name: String, + type: ArgumentType, + block: RequiredArgumentBuilder.() -> Unit = {} +): RequiredArgumentBuilder = RequiredArgumentBuilder.argument(name, type).apply(block) + +inline fun , AT> ArgumentBuilder.then( + name: String, + type: ArgumentType, + block: RequiredArgumentBuilder.() -> Unit = {} +): T = then(RequiredArgumentBuilder.argument(name, type).apply(block)) + +inline fun > ArgumentBuilder.then( + literal: String, + block: LiteralArgumentBuilder.() -> Unit = {} +): T = then(LiteralArgumentBuilder.literal(literal).apply(block)) \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/exposed/service/AbstractExposedDAOService.kt b/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/exposed/service/AbstractExposedDAOService.kt index f17cab77..2f5496f7 100644 --- a/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/exposed/service/AbstractExposedDAOService.kt +++ b/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/exposed/service/AbstractExposedDAOService.kt @@ -2,14 +2,14 @@ package dev.slne.surf.cloud.api.server.exposed.service import com.github.benmanes.caffeine.cache.Caffeine import com.sksamuel.aedile.core.asLoadingCache +import dev.slne.surf.cloud.api.server.plugin.CoroutineTransactional import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob -import org.checkerframework.checker.units.qual.K import org.jetbrains.exposed.dao.Entity -import org.jetbrains.exposed.sql.Transaction -import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.ApplicationContext import kotlin.coroutines.CoroutineContext /** @@ -25,6 +25,11 @@ abstract class AbstractExposedDAOService>( cacheCustomizer: Caffeine.() -> Unit = {}, private val context: CoroutineContext = Dispatchers.IO ) { + @Autowired + lateinit var applicationContext: ApplicationContext + + protected val self get() = applicationContext.getBean(javaClass) + /** * Cache for storing loaded entities, using Caffeine with coroutine-based loading. */ @@ -36,9 +41,7 @@ abstract class AbstractExposedDAOService>( val cacheContext = context + cacheName + SupervisorJob() asLoadingCache(CoroutineScope(cacheContext)) { - withTransaction { - load(it) - } + self.load(it) } } @@ -51,17 +54,6 @@ abstract class AbstractExposedDAOService>( protected abstract suspend fun load(key: K): V? protected abstract suspend fun create(key: K): V? - /** - * Executes a suspendable transaction within the configured coroutine context. - * - * @param block The transactional block to execute. - * @return The result of the transaction. - */ - protected suspend fun withTransaction(block: suspend Transaction.() -> T) = - newSuspendedTransaction(context) { - block() - } - /** * Retrieves an entity from the cache or loads it if not present. * @@ -70,9 +62,10 @@ abstract class AbstractExposedDAOService>( */ protected suspend fun find(key: K) = cache.get(key) - protected suspend fun find(key: K, result: V.() -> R): R? = withTransaction { - val entity = cache.get(key) ?: return@withTransaction null - return@withTransaction entity.result() + @CoroutineTransactional + protected suspend fun find(key: K, result: V.() -> R): R? { + val entity = cache.get(key) ?: return null + return entity.result() } /** @@ -82,8 +75,13 @@ abstract class AbstractExposedDAOService>( * @param block The modification block to apply to the entity. * @return The updated entity, or `null` if not found. */ - protected suspend fun update(key: K, createIfMissing: Boolean = true, block: suspend V.() -> Unit) = withTransaction { - val entity = cache.get(key) ?: if (createIfMissing) create(key) else null + @CoroutineTransactional + protected suspend fun update( + key: K, + createIfMissing: Boolean = true, + block: suspend V.() -> Unit + ) { + val entity = cache.get(key) ?: if (createIfMissing) self.create(key) else null if (entity != null) { entity.block() cache.put(key, entity) diff --git a/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/netty/packet/NettyPacketServerExtensions.kt b/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/netty/packet/NettyPacketServerExtensions.kt index c5967e53..01c4d25a 100644 --- a/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/netty/packet/NettyPacketServerExtensions.kt +++ b/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/netty/packet/NettyPacketServerExtensions.kt @@ -2,18 +2,8 @@ package dev.slne.surf.cloud.api.server.netty.packet import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket import dev.slne.surf.cloud.api.server.server.ServerCloudServerManager -import dev.slne.surf.surfapi.core.api.util.logger -private val log = logger() -suspend fun NettyPacket.broadcast() { - ServerCloudServerManager.retrieveAllServers().forEach { server -> - try { - server.connection.send(this) - } catch (e: Throwable) { - log.atWarning() - .withCause(e) - .log("Failed to send packet in broadcast action to server ${server.displayName}") - } - } +fun NettyPacket.broadcast() { + ServerCloudServerManager.broadcast(this) } \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/plugin/AdditionalStandaloneConfiguration.kt b/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/plugin/AdditionalStandaloneConfiguration.kt index 4ec1ec73..56510f8b 100644 --- a/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/plugin/AdditionalStandaloneConfiguration.kt +++ b/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/plugin/AdditionalStandaloneConfiguration.kt @@ -1,10 +1,12 @@ package dev.slne.surf.cloud.api.server.plugin import dev.slne.surf.cloud.api.server.plugin.utils.PluginUtilProxies +import dev.slne.surf.surfapi.core.api.util.logger import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.reactive.awaitFirstOrNull import kotlinx.coroutines.suspendCancellableCoroutine +import org.aopalliance.aop.Advice import org.aspectj.lang.ProceedingJoinPoint import org.aspectj.lang.annotation.Around import org.aspectj.lang.annotation.Aspect @@ -16,28 +18,23 @@ import org.springframework.beans.factory.getBean import org.springframework.boot.autoconfigure.AutoConfigurationPackages import org.springframework.boot.autoconfigure.EnableAutoConfiguration import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration -import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer import org.springframework.cache.annotation.EnableCaching import org.springframework.context.ApplicationContext -import org.springframework.context.ConfigurableApplicationContext -import org.springframework.context.annotation.AdviceMode -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.context.annotation.Import +import org.springframework.context.annotation.* import org.springframework.core.KotlinDetector import org.springframework.core.Ordered import org.springframework.core.PriorityOrdered import org.springframework.core.annotation.AnnotatedElementUtils -import org.springframework.core.annotation.Order -import org.springframework.data.auditing.DateTimeProvider -import org.springframework.data.jpa.repository.config.EnableJpaAuditing -import org.springframework.data.jpa.repository.config.EnableJpaRepositories +import org.springframework.instrument.classloading.LoadTimeWeaver +import org.springframework.instrument.classloading.SimpleThrowawayClassLoader import org.springframework.scheduling.annotation.EnableAsync import org.springframework.stereotype.Component import org.springframework.transaction.annotation.EnableTransactionManagement +import java.lang.instrument.ClassFileTransformer +import java.lang.instrument.Instrumentation import java.lang.reflect.Method -import java.time.ZonedDateTime -import java.util.* +import java.security.ProtectionDomain +import java.util.concurrent.CopyOnWriteArrayList import kotlin.coroutines.Continuation import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED import kotlin.coroutines.resume @@ -46,9 +43,12 @@ import kotlin.coroutines.startCoroutine @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) -@EnableJpaRepositories @Configuration -@Import(JpaAuditingConfiguration::class, CoroutineTransactionalAspect::class) +@Import( + CoroutineTransactionalAspect::class, + LoadTimeWeavingConfiguration::class, + TransactionConfiguration::class +) @EnableAsync(mode = AdviceMode.ASPECTJ) @EnableCaching(mode = AdviceMode.ASPECTJ) @EnableAutoConfiguration( @@ -58,32 +58,100 @@ import kotlin.coroutines.startCoroutine ) annotation class AdditionalStandaloneConfiguration -@EnableJpaAuditing(dateTimeProviderRef = "auditingDateTimeProvider") -@Configuration(proxyBeanMethods = false) -class JpaAuditingConfiguration : PriorityOrdered { - @Bean - fun auditingDateTimeProvider() = DateTimeProvider { Optional.of(ZonedDateTime.now()) } - @Bean - fun enhanceScopeInitializer(ctx: ConfigurableApplicationContext): HibernatePropertiesCustomizer { - val bases = AutoConfigurationPackages.get(ctx) - val joined = bases.joinToString(separator = ",") +@EnableTransactionManagement(mode = AdviceMode.ASPECTJ) +@Configuration +class TransactionConfiguration + +@EnableLoadTimeWeaving(aspectjWeaving = EnableLoadTimeWeaving.AspectJWeaving.ENABLED) +@Configuration +class LoadTimeWeavingConfiguration(context: ApplicationContext) : LoadTimeWeavingConfigurer { + private val allowedPrefixes = AutoConfigurationPackages.get(context) + .map { it.trimEnd('.') + "." } +// .plus(listOf("org.springframework.")) + + val weaver = LoadTimeWeaverImpl( + LoadTimeWeaverImpl.getInstrumentation(), + context.classLoader ?: error("Application context does not have a class loader"), + allowedPrefixes + ) + + override fun getLoadTimeWeaver(): LoadTimeWeaver = weaver - return HibernatePropertiesCustomizer { props -> - props["hibernate.enhance.scan.packages"] = joined - props["hibernate.enhance.managed.packages"] = joined + class LoadTimeWeaverImpl( + private val instrumentation: Instrumentation, + private val classLoader: ClassLoader, + private val allowedPrefixes: List + ) : LoadTimeWeaver { + private val transformers = CopyOnWriteArrayList() + + override fun addTransformer(transformer: ClassFileTransformer) { + val actualTransformer = + FilteringClassFileTransformer(transformer, classLoader, allowedPrefixes) + if (transformers.addIfAbsent(actualTransformer)) { + instrumentation.addTransformer(actualTransformer) + } } - } - override fun getOrder(): Int { - return PriorityOrdered.HIGHEST_PRECEDENCE - } -} + override fun getInstrumentableClassLoader(): ClassLoader = classLoader + override fun getThrowawayClassLoader(): ClassLoader = + SimpleThrowawayClassLoader(classLoader) -@EnableTransactionManagement(mode = AdviceMode.ASPECTJ) -@Configuration -class TransactionConfiguration + class FilteringClassFileTransformer( + private val targetTransformer: ClassFileTransformer, + private val targetClassLoader: ClassLoader, + private val allowedPrefixes: List + ) : ClassFileTransformer { + override fun transform( + loader: ClassLoader?, + className: String?, + classBeingRedefined: Class<*>?, + protectionDomain: ProtectionDomain?, + classfileBuffer: ByteArray? + ): ByteArray? { + if (loader != targetClassLoader || className == null) return null + val dotted = className.replace('/', '.') + if (allowedPrefixes.none { dotted.startsWith(it) }) { + return null + } + + return try { + targetTransformer.transform( + loader, + className, + classBeingRedefined, + protectionDomain, + classfileBuffer + ) + } catch (ex: Throwable) { + log.atWarning() + .withCause(ex) + .log("LTW: skipping weaving for $dotted due to ${ex.javaClass.simpleName}: ${ex.message}") + null + } + } + } + + companion object { + private val log = logger() + private const val AGENT_CLASS = + "org.springframework.instrument.InstrumentationSavingAgent" + private val agentClass: Class<*> by lazy { + Class.forName(AGENT_CLASS, true, javaClass.classLoader.parent) + } + private val agentMethod by lazy { + agentClass.getDeclaredMethod( + "getInstrumentation", + ) + } + + fun getInstrumentation(): Instrumentation { + return agentMethod.invoke(null) as Instrumentation + } + } + } +} /** * **Internal** Aspect that wraps every *suspending* call annotated (directly or @@ -122,8 +190,7 @@ class TransactionConfiguration */ @Aspect @Component -@Order(Ordered.LOWEST_PRECEDENCE) -class CoroutineTransactionalAspect(private val context: ApplicationContext) { +class CoroutineTransactionalAspect(private val context: ApplicationContext) : Advice, PriorityOrdered { /** * Around-advice for any method or class annotated with @@ -214,4 +281,7 @@ class CoroutineTransactionalAspect(private val context: ApplicationContext) { ) } + override fun getOrder(): Int { + return Ordered.LOWEST_PRECEDENCE + } } \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/plugin/CoroutineTransactional.kt b/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/plugin/CoroutineTransactional.kt index 94d83d0e..be2d5b5a 100644 --- a/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/plugin/CoroutineTransactional.kt +++ b/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/plugin/CoroutineTransactional.kt @@ -1,5 +1,6 @@ package dev.slne.surf.cloud.api.server.plugin +import kotlinx.coroutines.CoroutineScope import org.springframework.aot.hint.annotation.Reflective import java.lang.annotation.Inherited import kotlin.annotation.AnnotationRetention.RUNTIME @@ -59,4 +60,13 @@ annotation class CoroutineTransactional( FALSE(false), DEFAULT(null); } + + interface ScopeProvider { + /** + * Provides the current coroutine scope for the transaction. + * This is used to ensure that the transaction is suspended correctly + * when the coroutine yields. + */ + fun getCurrentScope(): CoroutineScope + } } diff --git a/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/plugin/utils/plugin-utils.kt b/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/plugin/utils/plugin-utils.kt index 3da85558..127c7b52 100644 --- a/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/plugin/utils/plugin-utils.kt +++ b/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/plugin/utils/plugin-utils.kt @@ -12,3 +12,29 @@ val currentDb getCallerClass()?.classLoader as? SpringPluginClassloader ?: error("Not in plugin classloader!") ) + +fun pluginContext(classloader: SpringPluginClassloader) = classloader.context +val context + get() = pluginContext( + getCallerClass()?.classLoader as? SpringPluginClassloader + ?: error("Not in plugin classloader!") + ) + +val currentContext + get() = pluginContext( + getCallerClass()?.classLoader as? SpringPluginClassloader + ?: error("Not in plugin classloader!") + ) + +fun bean(clazz: Class, classloader: SpringPluginClassloader): T { + return classloader.context.getBean(clazz) +} + +fun bean(clazz: Class): T { + return bean(clazz, getCallerClass()?.classLoader as? SpringPluginClassloader + ?: error("Not in plugin classloader!")) +} + +inline fun bean(): T { + return bean(T::class.java) +} \ No newline at end of file diff --git a/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/server/ServerCloudServerManager.kt b/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/server/ServerCloudServerManager.kt index 2bcc2403..577c8f58 100644 --- a/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/server/ServerCloudServerManager.kt +++ b/surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/server/ServerCloudServerManager.kt @@ -1,5 +1,6 @@ package dev.slne.surf.cloud.api.server.server +import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket import dev.slne.surf.cloud.api.common.server.CloudServerManager import dev.slne.surf.cloud.api.common.server.CommonCloudServer import it.unimi.dsi.fastutil.objects.ObjectCollection @@ -20,6 +21,10 @@ interface ServerCloudServerManager : CloudServerManager { override suspend fun retrieveServersInGroup(group: String): ObjectList override suspend fun retrieveServersByCategory(category: String): ObjectList override suspend fun retrieveAllServers(): ObjectCollection + override suspend fun retrieveServers(): ObjectCollection + override suspend fun retrieveProxies(): ObjectCollection + + fun broadcast(packet: NettyPacket) companion object : ServerCloudServerManager by CloudServerManager.instance as ServerCloudServerManager diff --git a/surf-cloud-bom/build.gradle.kts b/surf-cloud-bom/build.gradle.kts new file mode 100644 index 00000000..03a02fa7 --- /dev/null +++ b/surf-cloud-bom/build.gradle.kts @@ -0,0 +1,28 @@ +import dev.slne.surf.surfapi.gradle.util.slneReleases + +plugins { + `java-platform` + `maven-publish` +} + +javaPlatform { + allowDependencies() +} + +dependencies { + api(platform("org.springframework.boot:spring-boot-dependencies:3.5.3")) + api(platform("io.ktor:ktor-bom:3.2.1")) + api(platform("org.jetbrains.kotlin-wrappers:kotlin-wrappers-bom:2025.7.8")) +} + +publishing { + publications { + create("mavenBom") { + from(components["javaPlatform"]) + } + } + + repositories { + slneReleases() + } +} \ No newline at end of file diff --git a/surf-cloud-bukkit/build.gradle.kts b/surf-cloud-bukkit/build.gradle.kts index 31e3e201..48300af1 100644 --- a/surf-cloud-bukkit/build.gradle.kts +++ b/surf-cloud-bukkit/build.gradle.kts @@ -2,13 +2,13 @@ import dev.slne.surf.surfapi.gradle.util.registerRequired import dev.slne.surf.surfapi.gradle.util.registerSoft plugins { - id("dev.slne.surf.surfapi.gradle.paper-plugin") `exclude-kotlin` + id("dev.slne.surf.surfapi.gradle.paper-plugin") } surfPaperPluginApi { - mainClass("dev.slne.surf.cloud.bukkit.BukkitMain") - bootstrapper("dev.slne.surf.cloud.bukkit.BukkitBootstrap") + mainClass("dev.slne.surf.cloud.bukkit.PaperMain") + bootstrapper("dev.slne.surf.cloud.bukkit.PaperBootstrap") authors.add("twisti") generateLibraryLoader(false) diff --git a/surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/BukkitBootstrap.kt b/surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/PaperBootstrap.kt similarity index 93% rename from surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/BukkitBootstrap.kt rename to surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/PaperBootstrap.kt index d6a497c0..f8c45676 100644 --- a/surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/BukkitBootstrap.kt +++ b/surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/PaperBootstrap.kt @@ -11,7 +11,7 @@ import org.bukkit.plugin.java.JavaPlugin import kotlin.system.exitProcess @Suppress("UnstableApiUsage", "unused") -class BukkitBootstrap : PluginBootstrap { +class PaperBootstrap : PluginBootstrap { override fun bootstrap(context: BootstrapContext): Unit = runBlocking { try { @@ -28,6 +28,6 @@ class BukkitBootstrap : PluginBootstrap { } override fun createPlugin(context: PluginProviderContext): JavaPlugin { - return BukkitMain() + return PaperMain() } } \ No newline at end of file diff --git a/surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/BukkitMain.kt b/surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/PaperMain.kt similarity index 84% rename from surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/BukkitMain.kt rename to surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/PaperMain.kt index c215cd2b..e54cc118 100644 --- a/surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/BukkitMain.kt +++ b/surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/PaperMain.kt @@ -18,10 +18,13 @@ import net.kyori.adventure.text.format.NamedTextColor import org.bukkit.Location import org.bukkit.entity.Player import org.bukkit.event.server.ServerLoadEvent +import java.net.DatagramSocket +import java.net.InetSocketAddress +import java.net.URI import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract -class BukkitMain : SuspendingJavaPlugin() { +class PaperMain : SuspendingJavaPlugin() { override suspend fun onLoadAsync() { try { coreCloudInstance.onLoad() @@ -43,6 +46,21 @@ class BukkitMain : SuspendingJavaPlugin() { serverLoaded = true launch { + val datagramSocketIp = DatagramSocket().run { + connect(InetSocketAddress("8.8.8.8", 53)) + val ip = localAddress.hostAddress + close() + ip + } + + val publicIp = URI("https://checkip.amazonaws.com").toURL().readText().trim() + + repeat(20) { + println("Ip: ${server.ip}") + println("Public IP: $publicIp") + println("Datagram Socket IP: $datagramSocketIp") + } + try { coreCloudInstance.afterStart() } catch (t: Throwable) { @@ -133,9 +151,9 @@ class BukkitMain : SuspendingJavaPlugin() { companion object { @JvmStatic - val instance: BukkitMain - get() = getPlugin(BukkitMain::class.java) + val instance: PaperMain + get() = getPlugin(PaperMain::class.java) } } -val plugin get() = BukkitMain.instance +val plugin get() = PaperMain.instance diff --git a/surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/listener/player/ConnectionListener.kt b/surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/listener/player/ConnectionListener.kt index 715382a1..00568985 100644 --- a/surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/listener/player/ConnectionListener.kt +++ b/surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/listener/player/ConnectionListener.kt @@ -2,11 +2,11 @@ package dev.slne.surf.cloud.bukkit.listener.player import dev.slne.surf.cloud.api.client.netty.packet.fireAndAwait import dev.slne.surf.cloud.api.client.netty.packet.fireAndForget +import dev.slne.surf.cloud.api.common.player.task.PrePlayerJoinTask import dev.slne.surf.cloud.core.common.data.CloudPersistentData import dev.slne.surf.cloud.core.common.messages.MessageManager import dev.slne.surf.cloud.core.common.netty.network.protocol.running.PlayerConnectToServerPacket import dev.slne.surf.cloud.core.common.netty.network.protocol.running.PlayerDisconnectFromServerPacket -import dev.slne.surf.cloud.core.common.player.task.PrePlayerJoinTask import kotlinx.coroutines.runBlocking import org.bukkit.event.EventHandler import org.bukkit.event.EventPriority diff --git a/surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/netty/network/BukkitSpecificPacketListenerExtension.kt b/surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/netty/network/BukkitSpecificPacketListenerExtension.kt index f3c6d435..96d81696 100644 --- a/surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/netty/network/BukkitSpecificPacketListenerExtension.kt +++ b/surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/netty/network/BukkitSpecificPacketListenerExtension.kt @@ -1,27 +1,40 @@ package dev.slne.surf.cloud.bukkit.netty.network +import com.github.shynixn.mccoroutine.folia.entityDispatcher import com.github.shynixn.mccoroutine.folia.globalRegionDispatcher import com.github.shynixn.mccoroutine.folia.launch import dev.slne.surf.cloud.api.common.player.teleport.TeleportCause import dev.slne.surf.cloud.api.common.player.teleport.TeleportFlag import dev.slne.surf.cloud.api.common.player.teleport.TeleportLocation import dev.slne.surf.cloud.api.common.player.toCloudPlayer +import dev.slne.surf.cloud.api.common.util.observer.observingFlow import dev.slne.surf.cloud.bukkit.listener.player.SilentDisconnectListener import dev.slne.surf.cloud.bukkit.plugin import dev.slne.surf.cloud.core.client.netty.network.PlatformSpecificPacketListenerExtension +import dev.slne.surf.cloud.core.client.server.ClientCloudServerImpl +import dev.slne.surf.cloud.core.common.coroutines.CommonObservableScope import dev.slne.surf.cloud.core.common.netty.network.protocol.running.RegistrationInfo import dev.slne.surf.cloud.core.common.netty.network.protocol.running.ServerboundTransferPlayerPacketResponse import dev.slne.surf.surfapi.bukkit.api.extensions.server -import dev.slne.surf.surfapi.bukkit.api.util.dispatcher +import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution +import dev.slne.surf.surfapi.bukkit.api.nms.bridges.nmsCommonBridge +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.future.await import net.kyori.adventure.text.Component import org.bukkit.Bukkit +import java.net.InetAddress import java.net.InetSocketAddress import java.util.* import org.springframework.stereotype.Component as SpringComponent @SpringComponent class BukkitSpecificPacketListenerExtension : PlatformSpecificPacketListenerExtension { + override val playAddress: InetSocketAddress by lazy { + InetSocketAddress(InetAddress.getByName(server.ip), server.port) + } + override fun isServerManagedByThisProxy(address: InetSocketAddress): Boolean { error("Requested wrong server! This packet can only be acknowledged on a proxy!") } @@ -35,7 +48,7 @@ class BukkitSpecificPacketListenerExtension : PlatformSpecificPacketListenerExte override fun disconnectPlayer(playerUuid: UUID, reason: Component) { val player = Bukkit.getPlayer(playerUuid) ?: return - plugin.launch(player.dispatcher()) { + plugin.launch(plugin.entityDispatcher(player)) { player.kick(reason) } } @@ -61,6 +74,9 @@ class BukkitSpecificPacketListenerExtension : PlatformSpecificPacketListenerExte error("Requested wrong server! This packet can only be acknowledged on a proxy!") } + override fun registerCloudServerToProxy(client: ClientCloudServerImpl) = Unit + override fun unregisterCloudServerFromProxy(client: ClientCloudServerImpl) = Unit + override suspend fun teleportPlayerToPlayer( uuid: UUID, target: UUID @@ -84,4 +100,56 @@ class BukkitSpecificPacketListenerExtension : PlatformSpecificPacketListenerExte override fun shutdown() { server.shutdown() } + + @OptIn(NmsUseWithCaution::class) + override fun setVelocitySecret(secret: ByteArray) { + BukkitVelocitySecretManager.currentVelocityEnabled = true + BukkitVelocitySecretManager.currentVelocitySecret = secret + BukkitVelocitySecretManager.currentOnlineMode = false + } + + @OptIn(NmsUseWithCaution::class) + object BukkitVelocitySecretManager { + + @Volatile + var currentVelocityEnabled = nmsCommonBridge.isVelocityEnabled() + + @Volatile + var currentVelocitySecret = nmsCommonBridge + .getVelocitySecret() + .toByteArray() + + @Volatile + var currentOnlineMode = server.onlineMode + + init { + observingFlow({ nmsCommonBridge.isVelocityEnabled() }) + .onEach { remote -> + if (remote != currentVelocityEnabled) { + nmsCommonBridge.setVelocityEnabled(currentVelocityEnabled) + } + } + .launchIn(CommonObservableScope) + + + observingFlow({ nmsCommonBridge.getVelocitySecret() }) + .map { it.toByteArray() } + .onEach { remote -> + if (!remote.contentEquals(currentVelocitySecret)) { + nmsCommonBridge.setVelocitySecret( + currentVelocitySecret.decodeToString() + ) + } + } + .launchIn(CommonObservableScope) + + observingFlow({ server.onlineMode }) + .onEach { remote -> + if (remote != currentOnlineMode) { + nmsCommonBridge.setOnlineMode(remote) + } + } + .launchIn(CommonObservableScope) + } + } } \ No newline at end of file diff --git a/surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/netty/sync/ClientInformationUpdaterSyncer.kt b/surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/netty/sync/ClientInformationUpdaterSyncer.kt index 9ca07703..792bbc8f 100644 --- a/surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/netty/sync/ClientInformationUpdaterSyncer.kt +++ b/surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/netty/sync/ClientInformationUpdaterSyncer.kt @@ -3,8 +3,8 @@ package dev.slne.surf.cloud.bukkit.netty.sync import com.destroystokyo.paper.event.server.WhitelistToggleEvent import dev.slne.surf.cloud.api.client.netty.packet.fireAndForget import dev.slne.surf.cloud.api.common.server.state.ServerState -import dev.slne.surf.cloud.api.common.util.ObservableField import dev.slne.surf.cloud.api.common.util.TimeLogger +import dev.slne.surf.cloud.api.common.util.observer.ObservableField import dev.slne.surf.cloud.bukkit.util.ObservableFieldByEvent import dev.slne.surf.cloud.core.common.netty.network.protocol.running.ClientInformation import dev.slne.surf.cloud.core.common.netty.network.protocol.running.ServerboundClientInformationPacket diff --git a/surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/util/ObservableFieldByEvent.kt b/surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/util/ObservableFieldByEvent.kt index 20f05568..2d67ccdc 100644 --- a/surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/util/ObservableFieldByEvent.kt +++ b/surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/util/ObservableFieldByEvent.kt @@ -1,7 +1,7 @@ package dev.slne.surf.cloud.bukkit.util -import dev.slne.surf.cloud.api.common.util.ObservableField.ObservableCoroutineScope import dev.slne.surf.cloud.api.common.util.mutableObjectSetOf +import dev.slne.surf.cloud.api.common.util.observer.ObservableField.ObservableCoroutineScope import dev.slne.surf.surfapi.bukkit.api.event.listen import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope diff --git a/surf-cloud-core/surf-cloud-core-client/build.gradle.kts b/surf-cloud-core/surf-cloud-core-client/build.gradle.kts index 0b794c9d..d71b95a1 100644 --- a/surf-cloud-core/surf-cloud-core-client/build.gradle.kts +++ b/surf-cloud-core/surf-cloud-core-client/build.gradle.kts @@ -1,4 +1,5 @@ plugins { + `exclude-kotlin` id("dev.slne.surf.surfapi.gradle.core") } diff --git a/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/config/ClientConfig.kt b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/config/ClientConfig.kt new file mode 100644 index 00000000..bfa1fb99 --- /dev/null +++ b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/config/ClientConfig.kt @@ -0,0 +1,20 @@ +package dev.slne.surf.cloud.core.client.config + +import dev.slne.surf.cloud.core.common.coreCloudInstance +import dev.slne.surf.surfapi.core.api.config.createSpongeYmlConfig +import dev.slne.surf.surfapi.core.api.config.surfConfigApi +import org.spongepowered.configurate.objectmapping.ConfigSerializable +import org.spongepowered.configurate.objectmapping.meta.Comment +import org.spongepowered.configurate.objectmapping.meta.Setting + +val clientConfig: ClientConfig by lazy { + surfConfigApi.createSpongeYmlConfig(coreCloudInstance.dataFolder, "client-config.yml") +} + +@ConfigSerializable +data class ClientConfig( + + @Comment("Whether this server is considered as a lobby server.") + @Setting("isLobby") + val isLobby: Boolean = false, +) \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/ClientNettyClientImpl.kt b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/ClientNettyClientImpl.kt index 364de730..f8e49531 100644 --- a/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/ClientNettyClientImpl.kt +++ b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/ClientNettyClientImpl.kt @@ -5,9 +5,13 @@ import dev.slne.surf.cloud.api.common.exceptions.ExitCodes import dev.slne.surf.cloud.api.common.exceptions.FatalSurfError import dev.slne.surf.cloud.api.common.netty.network.protocol.PacketFlow import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket +import dev.slne.surf.cloud.api.common.netty.packet.RespondingNettyPacket import dev.slne.surf.cloud.api.common.server.CloudServerConstants -import dev.slne.surf.cloud.api.common.util.mutableObjectListOf -import dev.slne.surf.cloud.core.client.netty.network.* +import dev.slne.surf.cloud.core.client.config.clientConfig +import dev.slne.surf.cloud.core.client.netty.network.ClientHandshakePacketListenerImpl +import dev.slne.surf.cloud.core.client.netty.network.ClientRunningPacketListenerImpl +import dev.slne.surf.cloud.core.client.netty.network.PlatformSpecificPacketListenerExtension +import dev.slne.surf.cloud.core.client.netty.network.StatusUpdate import dev.slne.surf.cloud.core.client.server.serverManagerImpl import dev.slne.surf.cloud.core.common.config.cloudConfig import dev.slne.surf.cloud.core.common.coroutines.ConnectionTickScope @@ -34,7 +38,7 @@ import kotlin.time.Duration.Companion.seconds class ClientNettyClientImpl( val proxy: Boolean, - val platformExtension: PlatformSpecificPacketListenerExtension + val platformExtension: PlatformSpecificPacketListenerExtension, ) : CommonNettyClientImpl( CloudPersistentData.SERVER_ID, CloudProperties.SERVER_CATEGORY, @@ -43,45 +47,34 @@ class ClientNettyClientImpl( private val log = logger() private var _listener: ClientRunningPacketListenerImpl? = null - set(value) { - field = value - if (value != null) { - initConnection(value.connection) - } - } - val listener get() = _listener ?: error("listener not yet set") val connected get() = _listener?.connection?.connected ?: false - private val awaitPreRunning = CompletableDeferred() - private val finalizeHandler = mutableObjectListOf Unit>() + val preRunningCallback = CompletableDeferred() + val synchronizeCallback = CompletableDeferred() + lateinit var startSynchronizeTask: suspend () -> Unit + + override val playAddress: InetSocketAddress + get() = platformExtension.playAddress private val statusUpdate: StatusUpdate = { log.atInfo().log(it) } + override var velocitySecret = ByteArray(0) + /** * Bootstraps the client. Setup the connection protocol until the PreRunning state. */ suspend fun bootstrap() { val config = cloudConfig.connectionConfig.nettyConfig connectToServer(ServerAddress(config.host, config.port)) - awaitPreRunning.await() // Wait until the connection is in the PreRunning state - } - - /** - * Finalizes the client. - * This method is called after all other plugins have registered their packets. - * Switches to the Running state. - */ - suspend fun finalize() { - finalizeHandler.forEach { it() } - finalizeHandler.clear() + preRunningCallback.await() // Wait until the connection is in the PreRunning state } - fun finalizeHandler(handler: suspend () -> Unit) = finalizeHandler.add(handler) suspend fun stop() { + velocitySecret.fill(0) doShutdown() } @@ -119,16 +112,17 @@ class ClientNettyClientImpl( connection, platformExtension, statusUpdate, - awaitPreRunning ), false ) + CloudProperties connection.send( ServerboundLoginStartPacket( serverId, serverCategory, serverName, - proxy + proxy, + clientConfig.isLobby, ) ) } catch (e: Exception) { @@ -209,6 +203,7 @@ class ClientNettyClientImpl( } override fun broadcast(packets: List) { + require(packets.none { it is RespondingNettyPacket<*> }) { "Cannot broadcast responding packets." } val finalPackets = packets.toMutableList() finalPackets.add(0, ServerboundBroadcastPacket) diff --git a/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/NettyCommonClientManager.kt b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/NettyCommonClientManager.kt index e30b535a..335e38e9 100644 --- a/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/NettyCommonClientManager.kt +++ b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/NettyCommonClientManager.kt @@ -39,11 +39,15 @@ abstract class NettyCommonClientManager( } } - override suspend fun afterStart(timeLogger: TimeLogger) { - timeLogger.measureStep("Finalize Netty client") { - nettyClient.finalize() + override suspend fun onEnable(timeLogger: TimeLogger) { + super.onEnable(timeLogger) + timeLogger.measureStep("Synchronizing Netty client") { + nettyClient.startSynchronizeTask() + nettyClient.synchronizeCallback.await() } + } + override suspend fun afterStart(timeLogger: TimeLogger) { super.afterStart(timeLogger) } diff --git a/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/AbstractStatusUpdater.kt b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/AbstractStatusUpdater.kt index a5e98477..47e51cf3 100644 --- a/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/AbstractStatusUpdater.kt +++ b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/AbstractStatusUpdater.kt @@ -26,7 +26,10 @@ abstract class AbstractStatusUpdater(initialState: State, val updateStatus: Stat PREPARE_CONNECTION("Preparing connection...", setOf(ENCRYPTING, CONNECTING)), PRE_PRE_RUNNING("Running initial setup...", setOf(PREPARE_CONNECTION)), PRE_RUNNING("Waiting till client started...", setOf(PRE_PRE_RUNNING)), - CONNECTED("Connected!", setOf(PRE_RUNNING)) + SYNCHRONIZING("Synchronizing...", setOf(PRE_RUNNING)), + SYNCHRONIZE_WAIT_FOR_SERVER("Waiting for server to finish synchronization...", setOf(SYNCHRONIZING)), + SYNCHRONIZED("Finished synchronization!", setOf(SYNCHRONIZING, SYNCHRONIZE_WAIT_FOR_SERVER)), + CONNECTED("Connected!", setOf(SYNCHRONIZED)) } } diff --git a/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/ClientHandshakePacketListenerImpl.kt b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/ClientHandshakePacketListenerImpl.kt index be56fc71..cbb7e277 100644 --- a/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/ClientHandshakePacketListenerImpl.kt +++ b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/ClientHandshakePacketListenerImpl.kt @@ -7,7 +7,6 @@ import dev.slne.surf.cloud.core.common.netty.network.protocol.login.ClientLoginP import dev.slne.surf.cloud.core.common.netty.network.protocol.login.ClientboundLoginFinishedPacket import dev.slne.surf.cloud.core.common.netty.network.protocol.login.ServerboundLoginAcknowledgedPacket import dev.slne.surf.cloud.core.common.netty.network.protocol.prerunning.PreRunningProtocols -import kotlinx.coroutines.CompletableDeferred class ClientHandshakePacketListenerImpl( @@ -15,18 +14,17 @@ class ClientHandshakePacketListenerImpl( val connection: ConnectionImpl, val platformExtension: PlatformSpecificPacketListenerExtension, updateStatus: StatusUpdate, - val awaitFinishPreRunning: CompletableDeferred ) : AbstractStatusUpdater(State.CONNECTING, updateStatus), ClientLoginPacketListener { override suspend fun handleLoginFinished(packet: ClientboundLoginFinishedPacket) { switchState(State.PREPARE_CONNECTION) + client.initConnection(connection) val listener = ClientPreRunningPacketListenerImpl( client, connection, platformExtension, this, - awaitFinishPreRunning ) connection.setupInboundProtocol( PreRunningProtocols.CLIENTBOUND, diff --git a/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/ClientPreRunningPacketListenerImpl.kt b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/ClientPreRunningPacketListenerImpl.kt index 49ebeb01..103449f7 100644 --- a/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/ClientPreRunningPacketListenerImpl.kt +++ b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/ClientPreRunningPacketListenerImpl.kt @@ -6,7 +6,7 @@ import dev.slne.surf.cloud.core.client.netty.network.AbstractStatusUpdater.State import dev.slne.surf.cloud.core.common.netty.network.ConnectionImpl import dev.slne.surf.cloud.core.common.netty.network.DisconnectionDetails import dev.slne.surf.cloud.core.common.netty.network.protocol.prerunning.* -import dev.slne.surf.cloud.core.common.netty.network.protocol.running.RunningProtocols +import dev.slne.surf.cloud.core.common.netty.network.protocol.synchronizing.SynchronizingProtocols import kotlinx.coroutines.CompletableDeferred class ClientPreRunningPacketListenerImpl( @@ -14,18 +14,20 @@ class ClientPreRunningPacketListenerImpl( connection: ConnectionImpl, val platformExtension: PlatformSpecificPacketListenerExtension, val statusUpdater: AbstractStatusUpdater, - val awaitFinishPreRunning: CompletableDeferred ) : ClientCommonPacketListenerImpl(connection), ClientPreRunningPacketListener { private val completion = CompletableDeferred() init { - client.finalizeHandler { proceedToRunningState() } + client.startSynchronizeTask = { + proceedToSynchronizingState() + completion.await() + } } private fun finishPreRunning() { statusUpdater.switchState(State.PRE_RUNNING) - awaitFinishPreRunning.complete(Unit) + client.preRunningCallback.complete(Unit) } override suspend fun handlePreRunningFinished(packet: ClientboundPreRunningFinishedPacket) { @@ -33,24 +35,28 @@ class ClientPreRunningPacketListenerImpl( connection.send(ServerboundPreRunningAcknowledgedPacket) } - override suspend fun handleReadyToRun(packet: ClientboundReadyToRunPacket) { - val listener = ClientRunningPacketListenerImpl(connection, client, platformExtension) + override suspend fun handleProceedToSynchronizing(packet: ClientboundProceedToSynchronizingPacket) { + val listener = ClientSynchronizingPacketListenerImpl( + client, + connection, + platformExtension, + statusUpdater + ) connection.setupInboundProtocol( - RunningProtocols.CLIENTBOUND, + SynchronizingProtocols.CLIENTBOUND, listener ) - connection.send(ServerboundReadyToRunPacket) - connection.setupOutboundProtocol(RunningProtocols.SERVERBOUND) + connection.send(ServerboundProceedToSynchronizingAcknowledgedPacket(client.playAddress)) + connection.setupOutboundProtocol(SynchronizingProtocols.SERVERBOUND) - client.initListener(listener) - statusUpdater.switchState(State.CONNECTED) completion.complete(Unit) + listener.startSynchronizing() } - suspend fun proceedToRunningState() { + + fun proceedToSynchronizingState() { check(statusUpdater.getState() == State.PRE_RUNNING) { "Cannot proceed to running state from ${statusUpdater.getState()}" } send(ServerboundRequestContinuation) - completion.await() } override suspend fun onDisconnect(details: DisconnectionDetails) { diff --git a/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/ClientRunningPacketListenerImpl.kt b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/ClientRunningPacketListenerImpl.kt index b5f842d3..bfeb770b 100644 --- a/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/ClientRunningPacketListenerImpl.kt +++ b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/ClientRunningPacketListenerImpl.kt @@ -3,6 +3,7 @@ package dev.slne.surf.cloud.core.client.netty.network import com.google.common.flogger.StackSize import dev.slne.surf.cloud.api.common.event.offlineplayer.punishment.CloudPlayerPunishEvent import dev.slne.surf.cloud.api.common.event.offlineplayer.punishment.CloudPlayerPunishmentUpdatedEvent +import dev.slne.surf.cloud.api.common.netty.network.ConnectionProtocol import dev.slne.surf.cloud.api.common.netty.network.protocol.respond import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket import dev.slne.surf.cloud.api.common.netty.packet.NettyPacketInfo @@ -13,15 +14,16 @@ import dev.slne.surf.cloud.core.client.player.commonPlayerManagerImpl import dev.slne.surf.cloud.core.client.server.ClientCloudServerImpl import dev.slne.surf.cloud.core.client.server.ClientProxyCloudServerImpl import dev.slne.surf.cloud.core.client.server.serverManagerImpl +import dev.slne.surf.cloud.core.client.sync.SyncRegistryImpl import dev.slne.surf.cloud.core.client.util.getOrLoadUser import dev.slne.surf.cloud.core.client.util.luckperms import dev.slne.surf.cloud.core.common.coroutines.PacketHandlerScope import dev.slne.surf.cloud.core.common.netty.network.ConnectionImpl +import dev.slne.surf.cloud.core.common.netty.network.protocol.common.ClientboundSetVelocitySecretPacket import dev.slne.surf.cloud.core.common.netty.network.protocol.running.* import dev.slne.surf.cloud.core.common.netty.registry.listener.NettyListenerRegistry import dev.slne.surf.cloud.core.common.player.playerManagerImpl import dev.slne.surf.cloud.core.common.player.task.PrePlayerJoinTaskManager -import dev.slne.surf.cloud.core.common.util.bean import dev.slne.surf.cloud.core.common.util.hasPermissionPlattform import dev.slne.surf.surfapi.core.api.messages.adventure.getPointer import dev.slne.surf.surfapi.core.api.messages.adventure.text @@ -41,13 +43,14 @@ class ClientRunningPacketListenerImpl( ) : ClientCommonPacketListenerImpl(connection), RunningClientPacketListener { private val log = logger() - override suspend fun handlePlayerConnectToServer(packet: PlayerConnectToServerPacket) { + override suspend fun handlePlayerConnectedToServer(packet: PlayerConnectedToServerPacket) { playerManagerImpl.updateOrCreatePlayer( packet.uuid, packet.name, packet.proxy, packet.playerIp, - packet.serverUid + packet.serverUid, + false ) } @@ -180,20 +183,28 @@ class ClientRunningPacketListenerImpl( packet.serverId, packet.group, packet.name, + packet.playAddress, ) } else { ClientCloudServerImpl( packet.serverId, packet.group, packet.name, - ) + packet.playAddress, + packet.lobby + ).also { client -> + platformExtension.registerCloudServerToProxy(client) + } } serverManagerImpl.registerServer(server) } override suspend fun handleUnregisterServerPacket(packet: ClientboundUnregisterServerPacket) { - serverManagerImpl.unregisterServer(packet.serverId) + val removed = serverManagerImpl.unregisterServer(packet.serverId) + if (removed is ClientCloudServerImpl) { + platformExtension.unregisterCloudServerFromProxy(removed) + } } override suspend fun handleAddPlayerToServer(packet: ClientboundAddPlayerToServerPacket) { @@ -259,16 +270,6 @@ class ClientRunningPacketListenerImpl( platformExtension.triggerShutdown() } - override suspend fun handleBatchUpdateServer(packet: ClientboundBatchUpdateServer) { - serverManagerImpl.batchUpdateServer(packet.servers.map { - if (it.proxy) { - ClientProxyCloudServerImpl(it.serverId, it.group, it.name) - } else { - ClientCloudServerImpl(it.serverId, it.group, it.name) - } - }) - } - override fun handleUpdateAFKState(packet: UpdateAFKStatePacket) { playerManagerImpl.getPlayer(packet.uuid)?.let { player -> require(player is ClientCloudPlayerImpl<*>) { "Player $player is not a client player" } @@ -278,7 +279,7 @@ class ClientRunningPacketListenerImpl( override suspend fun handleRunPlayerPreJoinTasks(packet: ClientboundRunPrePlayerJoinTasksPacket) { val player = commonPlayerManagerImpl.getOfflinePlayer(packet.uuid) - val result = bean().runTasks(player) + val result = PrePlayerJoinTaskManager.runTasks(player) packet.respond(RunPrePlayerJoinTasksResultPacket(result)) } @@ -320,11 +321,43 @@ class ClientRunningPacketListenerImpl( } } + override fun handleSyncValueChange(packet: SyncValueChangePacket) { + try { + if (!packet.registered) return + SyncRegistryImpl.instance.updateSyncValue(packet.syncId, packet.value) + } catch (e: Throwable) { + log.atWarning() + .withCause(e) + .log("Failed to update sync value for packet $packet") + } + } + + override fun handleSyncSetDelta(packet: SyncSetDeltaPacket) { + try { + SyncRegistryImpl.instance.handleSyncSetDelta(packet) + } catch (e: Throwable) { + log.atWarning() + .withCause(e) + .log("Failed to handle sync set delta for packet $packet") + } + } + + override fun handleSetVelocitySecret(packet: ClientboundSetVelocitySecretPacket) { + try { + client.velocitySecret = packet.secret + platformExtension.setVelocitySecret(packet.secret) + } catch (e: Throwable) { + log.atWarning() + .withCause(e) + .log("Failed to set velocity secret for packet $packet") + } + } + override fun handlePacket(packet: NettyPacket) { val listeners = NettyListenerRegistry.getListeners(packet.javaClass) ?: return if (listeners.isEmpty()) return - val info = NettyPacketInfo(connection) + val info = NettyPacketInfo(connection, ConnectionProtocol.RUNNING) for (listener in listeners) { PacketHandlerScope.launch { diff --git a/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/ClientSynchronizingPacketListenerImpl.kt b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/ClientSynchronizingPacketListenerImpl.kt new file mode 100644 index 00000000..4bec557b --- /dev/null +++ b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/ClientSynchronizingPacketListenerImpl.kt @@ -0,0 +1,162 @@ +package dev.slne.surf.cloud.core.client.netty.network + +import dev.slne.surf.cloud.api.common.netty.network.ConnectionProtocol +import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket +import dev.slne.surf.cloud.api.common.netty.packet.NettyPacketInfo +import dev.slne.surf.cloud.core.client.netty.ClientNettyClientImpl +import dev.slne.surf.cloud.core.client.server.ClientCloudServerImpl +import dev.slne.surf.cloud.core.client.server.ClientProxyCloudServerImpl +import dev.slne.surf.cloud.core.client.server.serverManagerImpl +import dev.slne.surf.cloud.core.client.sync.SyncRegistryImpl +import dev.slne.surf.cloud.core.common.coroutines.BeforeStartTaskScope +import dev.slne.surf.cloud.core.common.coroutines.PacketHandlerScope +import dev.slne.surf.cloud.core.common.netty.network.ConnectionImpl +import dev.slne.surf.cloud.core.common.netty.network.protocol.common.ClientboundSetVelocitySecretPacket +import dev.slne.surf.cloud.core.common.netty.network.protocol.running.ClientboundBatchUpdateServer +import dev.slne.surf.cloud.core.common.netty.network.protocol.running.RunningProtocols +import dev.slne.surf.cloud.core.common.netty.network.protocol.running.SyncSetDeltaPacket +import dev.slne.surf.cloud.core.common.netty.network.protocol.running.SyncValueChangePacket +import dev.slne.surf.cloud.core.common.netty.network.protocol.synchronizing.* +import dev.slne.surf.cloud.core.common.netty.registry.listener.NettyListenerRegistry +import dev.slne.surf.cloud.core.common.plugin.task.CloudSynchronizeTaskManager +import dev.slne.surf.surfapi.core.api.util.logger +import kotlinx.coroutines.launch + +class ClientSynchronizingPacketListenerImpl( + val client: ClientNettyClientImpl, + connection: ConnectionImpl, + val platformExtension: PlatformSpecificPacketListenerExtension, + val statusUpdater: AbstractStatusUpdater, +) : ClientCommonPacketListenerImpl(connection), ClientSynchronizingPacketListener { + + private val log = logger() + + fun startSynchronizing() { + statusUpdater.switchState(AbstractStatusUpdater.State.SYNCHRONIZING) + + BeforeStartTaskScope.launch { + CloudSynchronizeTaskManager.executeTasks(client) + + statusUpdater.switchState(AbstractStatusUpdater.State.SYNCHRONIZE_WAIT_FOR_SERVER) + send(FinishSynchronizingPacket) + } + } + + override suspend fun handleSynchronizeFinish(packet: ClientboundSynchronizeFinishPacket) { + statusUpdater.switchState(AbstractStatusUpdater.State.SYNCHRONIZED) + + val listener = ClientRunningPacketListenerImpl(connection, client, platformExtension) + connection.setupInboundProtocol(RunningProtocols.CLIENTBOUND, listener) + connection.send(ServerboundSynchronizeFinishAcknowledgedPacket) + connection.setupOutboundProtocol(RunningProtocols.SERVERBOUND) + + client.initListener(listener) + statusUpdater.switchState(AbstractStatusUpdater.State.CONNECTED) + client.synchronizeCallback.complete(Unit) + } + + override fun handleSyncValueChange(packet: SyncValueChangePacket) { + try { + if (!packet.registered) return + SyncRegistryImpl.instance.updateSyncValue(packet.syncId, packet.value) + } catch (e: Exception) { + log.atWarning() + .withCause(e) + .log("Failed to update sync value for packet $packet") + } + } + + override fun handleBatchSyncValue(packet: ClientboundBatchSyncValuePacket) { + try { + SyncRegistryImpl.instance.applyBatchSyncValue(packet.syncValues) + } catch (e: Exception) { + log.atWarning() + .withCause(e) + .log("Failed to apply batch sync values for packet $packet") + } + } + + override fun handleBatchSyncSet(packet: ClientboundBatchSyncSetPacket) { + try { + SyncRegistryImpl.instance.applyBatchSyncSets(packet.syncSets) + } catch (e: Exception) { + log.atWarning() + .withCause(e) + .log("Failed to apply batch sync sets for packet $packet") + } + } + + override suspend fun handleBatchUpdateServer(packet: ClientboundBatchUpdateServer) { + serverManagerImpl.batchUpdateServer(packet.servers.map { data -> + if (data.proxy) { + ClientProxyCloudServerImpl(data.serverId, data.group, data.name, data.playAddress) + } else { + ClientCloudServerImpl( + data.serverId, + data.group, + data.name, + data.playAddress, + data.lobby + ).also { server -> + platformExtension.registerCloudServerToProxy(server) + } + } + }) + } + + override fun handleSyncSetDelta(packet: SyncSetDeltaPacket) { + try { + SyncRegistryImpl.instance.handleSyncSetDelta(packet) + } catch (e: Exception) { + log.atWarning() + .withCause(e) + .log("Failed to handle sync set delta for packet $packet") + } + } + + override fun handleSetVelocitySecret(packet: ClientboundSetVelocitySecretPacket) { + try { + client.velocitySecret = packet.secret + platformExtension.setVelocitySecret(packet.secret) + } catch (e: Exception) { + log.atWarning() + .withCause(e) + .log("Failed to set velocity secret for packet $packet") + } + } + + override fun handlePacket(packet: NettyPacket) { + val listeners = NettyListenerRegistry.getListeners(packet.javaClass) ?: return + if (listeners.isEmpty()) return + + val info = NettyPacketInfo(connection, ConnectionProtocol.SYNCHRONIZING) + + for (listener in listeners) { + PacketHandlerScope.launch { + try { + listener.handle(packet, info) + } catch (e: Throwable) { + log.atWarning() + .withCause(e) + .log( + "Failed to call listener %s for packet %s", + listener::class.simpleName, + packet::class.simpleName + ) + } + } + } + } + + override fun restart() { + platformExtension.restart() + } + + override fun shutdown() { + platformExtension.shutdown() + } + + override fun isAcceptingMessages(): Boolean { + return connection.connected + } +} \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/PlatformSpecificPacketListenerExtension.kt b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/PlatformSpecificPacketListenerExtension.kt index ddf464f3..f0b399dc 100644 --- a/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/PlatformSpecificPacketListenerExtension.kt +++ b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/PlatformSpecificPacketListenerExtension.kt @@ -1,8 +1,9 @@ package dev.slne.surf.cloud.core.client.netty.network -import dev.slne.surf.cloud.api.common.player.teleport.TeleportLocation import dev.slne.surf.cloud.api.common.player.teleport.TeleportCause import dev.slne.surf.cloud.api.common.player.teleport.TeleportFlag +import dev.slne.surf.cloud.api.common.player.teleport.TeleportLocation +import dev.slne.surf.cloud.core.client.server.ClientCloudServerImpl import dev.slne.surf.cloud.core.common.netty.network.protocol.running.RegistrationInfo import dev.slne.surf.cloud.core.common.netty.network.protocol.running.ServerboundTransferPlayerPacketResponse import net.kyori.adventure.text.Component @@ -11,6 +12,8 @@ import java.util.* interface PlatformSpecificPacketListenerExtension { + val playAddress: InetSocketAddress + fun isServerManagedByThisProxy(address: InetSocketAddress): Boolean suspend fun transferPlayerToServer( @@ -30,8 +33,13 @@ interface PlatformSpecificPacketListenerExtension { ): Boolean fun registerCloudServersToProxy(packets: Array) + fun registerCloudServerToProxy(client: ClientCloudServerImpl) + fun unregisterCloudServerFromProxy(client: ClientCloudServerImpl) + suspend fun teleportPlayerToPlayer(uuid: UUID, target: UUID): Boolean + fun setVelocitySecret(secret: ByteArray) + fun triggerShutdown() fun restart() diff --git a/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/player/ClientCloudPlayerImpl.kt b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/player/ClientCloudPlayerImpl.kt index d7d13ec7..940d7021 100644 --- a/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/player/ClientCloudPlayerImpl.kt +++ b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/player/ClientCloudPlayerImpl.kt @@ -23,6 +23,7 @@ import dev.slne.surf.cloud.core.common.player.CommonCloudPlayerImpl import dev.slne.surf.cloud.core.common.player.ppdc.PersistentPlayerDataContainerImpl import dev.slne.surf.cloud.core.common.util.hasPermissionPlattform import dev.slne.surf.surfapi.core.api.messages.adventure.getPointer +import dev.slne.surf.surfapi.core.api.nbt.fast import net.kyori.adventure.audience.Audience import net.kyori.adventure.audience.MessageType import net.kyori.adventure.bossbar.BossBar @@ -78,6 +79,16 @@ abstract class ClientCloudPlayerImpl(uuid: UUID, name ?: error("Failed to get last server") } + override fun currentServer(): CloudServer { + val server = serverManagerImpl.getServerByIdUnsafe( + serverUid ?: error("Player is not connected to a server") + ) ?: error("Server not found for UID: $serverUid") + + require(server is CloudServer) { "Expected CloudServer, but got ${server::class.simpleName}" } + + return server + } + override suspend fun nameHistory(): NameHistory { return request(DataRequestType.NAME_HISTORY).history } @@ -110,13 +121,13 @@ abstract class ClientCloudPlayerImpl(uuid: UUID, name val response = ServerboundRequestPlayerPersistentDataContainer(uuid).fireAndAwaitOrThrow() val nbt = response.nbt - val container = PersistentPlayerDataContainerImpl(nbt) + val container = PersistentPlayerDataContainerImpl(nbt.fast(synchronize = true)) val result = container.block() ServerboundPlayerPersistentDataContainerUpdatePacket( uuid, response.verificationId, - container.toTagCompound() + container.toTagCompound() ).fireAndForget() return result diff --git a/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/server/ClientCloudServerImpl.kt b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/server/ClientCloudServerImpl.kt index 9b3c0a04..aa8a6767 100644 --- a/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/server/ClientCloudServerImpl.kt +++ b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/server/ClientCloudServerImpl.kt @@ -3,9 +3,16 @@ package dev.slne.surf.cloud.core.client.server import dev.slne.surf.cloud.api.client.netty.packet.fireAndForget import dev.slne.surf.cloud.core.common.netty.network.protocol.running.ServerboundShutdownServerPacket import dev.slne.surf.cloud.core.common.server.AbstractCloudServer +import java.net.InetSocketAddress -class ClientCloudServerImpl(uid: Long, group: String, name: String) : - AbstractCloudServer(uid, group, name) { +class ClientCloudServerImpl( + uid: Long, + group: String, + name: String, + playAddress: InetSocketAddress, + lobby: Boolean +) : + AbstractCloudServer(uid, group, name, playAddress, lobby) { override fun shutdown() { ServerboundShutdownServerPacket(uid).fireAndForget() } diff --git a/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/server/ClientProxyCloudServerImpl.kt b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/server/ClientProxyCloudServerImpl.kt index 4ab8e296..a590702f 100644 --- a/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/server/ClientProxyCloudServerImpl.kt +++ b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/server/ClientProxyCloudServerImpl.kt @@ -3,8 +3,14 @@ package dev.slne.surf.cloud.core.client.server import dev.slne.surf.cloud.api.client.netty.packet.fireAndForget import dev.slne.surf.cloud.core.common.netty.network.protocol.running.ServerboundShutdownServerPacket import dev.slne.surf.cloud.core.common.server.AbstractProxyCloudServer +import java.net.InetSocketAddress -class ClientProxyCloudServerImpl(uid: Long, group: String, name: String) : AbstractProxyCloudServer(uid, group, name) { +class ClientProxyCloudServerImpl( + uid: Long, + group: String, + name: String, + playAddress: InetSocketAddress, +) : AbstractProxyCloudServer(uid, group, name, playAddress) { override fun shutdown() { ServerboundShutdownServerPacket(uid).fireAndForget() } diff --git a/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/sync/SyncRegistryImpl.kt b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/sync/SyncRegistryImpl.kt new file mode 100644 index 00000000..67804bd4 --- /dev/null +++ b/surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/sync/SyncRegistryImpl.kt @@ -0,0 +1,65 @@ +package dev.slne.surf.cloud.core.client.sync + +import com.google.auto.service.AutoService +import dev.slne.surf.cloud.api.client.netty.packet.fireAndForget +import dev.slne.surf.cloud.api.common.sync.SyncRegistry +import dev.slne.surf.cloud.core.common.data.CloudPersistentData +import dev.slne.surf.cloud.core.common.netty.network.protocol.running.SyncSetDeltaPacket +import dev.slne.surf.cloud.core.common.netty.network.protocol.running.SyncValueChangePacket +import dev.slne.surf.cloud.core.common.sync.BasicSyncValue +import dev.slne.surf.cloud.core.common.sync.CommonSyncRegistryImpl +import dev.slne.surf.surfapi.core.api.util.logger +import dev.slne.surf.surfapi.core.api.util.mutableObject2LongMapOf + +@AutoService(SyncRegistry::class) +class SyncRegistryImpl : CommonSyncRegistryImpl() { + private val log = logger() + private val lastChangeIds = mutableObject2LongMapOf() + + override fun afterChange(syncValue: BasicSyncValue<*>) { + super.afterChange(syncValue) + SyncValueChangePacket(CloudPersistentData.SERVER_ID, syncValue).fireAndForget() + } + + fun applyBatchSyncValue(syncValues: List>) { + require(frozen) { "SyncRegistry is not frozen, cannot apply batch" } + + syncValues.forEach { (syncId, value) -> + updateSyncValue(syncId, value) + } + } + + fun handleSyncSetDelta(packet: SyncSetDeltaPacket) { + if (!packet.registered) return + val set = + getSet(packet.setId) ?: error("SyncSet with id '${packet.setId}' is not registered") + val lastChangeId = lastChangeIds.getLong(packet.setId) + if (packet.changeId <= lastChangeId) { + log.atInfo() + .log("Ignoring stale SyncSetDeltaPacket for set '${packet.setId}' with changeId ${packet.changeId}, last known changeId is $lastChangeId") + return + } + + if (packet.added) { + set.addInternal(packet.element) + } else { + set.removeInternal(packet.element) + } + + lastChangeIds[packet.setId] = packet.changeId + } + + fun applyBatchSyncSets(bulk: List>>) { + bulk.forEach { (id, snapshot) -> + val set = getSet(id) + if (set != null) { + set.addAllInternal(snapshot) + lastChangeIds[id] = Long.MAX_VALUE // Reset change ID to max after bulk update + } + } + } + + companion object { + val instance by lazy { CommonSyncRegistryImpl.instance as SyncRegistryImpl } + } +} \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/CloudCoreInstance.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/CloudCoreInstance.kt index 1271a3fa..87733f1b 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/CloudCoreInstance.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/CloudCoreInstance.kt @@ -11,6 +11,8 @@ import dev.slne.surf.cloud.api.common.util.forEachOrdered import dev.slne.surf.cloud.core.common.event.CloudEventListenerBeanPostProcessor import dev.slne.surf.cloud.core.common.netty.network.EncryptionManager import dev.slne.surf.cloud.core.common.player.punishment.CloudPlayerPunishmentManagerBridgeImpl +import dev.slne.surf.cloud.core.common.player.task.PrePlayerJoinTaskAutoRegistrationHandler +import dev.slne.surf.cloud.core.common.plugin.task.CloudBeforeStartTaskHandler import dev.slne.surf.cloud.core.common.processors.NettyPacketProcessor import dev.slne.surf.cloud.core.common.spring.CloudChildSpringApplicationConfiguration import dev.slne.surf.cloud.core.common.spring.CloudLifecycleAware @@ -53,7 +55,7 @@ class CloudCoreInstance : CloudInstance { } val springProfile by lazy { - ClassPathResource("spring.profile").getContentAsString( + ClassPathResource("spring.profile", javaClass.classLoader).getContentAsString( StandardCharsets.UTF_8 ) } @@ -159,12 +161,13 @@ class CloudCoreInstance : CloudInstance { timeLogger.printSummary() } - private fun startMainSpringApplication() = + private fun startMainSpringApplication() = tempChangeSystemClassLoader(javaClass.classLoader) { SpringApplicationBuilder(SurfCloudMainApplication::class.java) .profiles(springProfile) .initializers(NettyPacketProcessor()) .web(WebApplicationType.NONE) .run() + } override fun startSpringApplication( applicationClass: Class<*>, @@ -230,6 +233,14 @@ class CloudCoreInstance : CloudInstance { "loginValidationAutoRegistrationHandler", RootBeanDefinition(CloudPlayerPunishmentManagerBridgeImpl.LoginValidationAutoRegistrationHandler::class.java) ) + ctx.registerBeanDefinition( + "beforeStartTaskHandler", + RootBeanDefinition(CloudBeforeStartTaskHandler::class.java) + ) + ctx.registerBeanDefinition( + "prePlayerJoinTaskAutoRegistrationHandler", + RootBeanDefinition(PrePlayerJoinTaskAutoRegistrationHandler::class.java) + ) }) diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/coroutines/scopes.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/coroutines/scopes.kt index 39daf6d0..fa70595b 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/coroutines/scopes.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/coroutines/scopes.kt @@ -2,35 +2,37 @@ package dev.slne.surf.cloud.core.common.coroutines import dev.slne.surf.cloud.api.common.util.mutableObjectListOf import dev.slne.surf.cloud.api.common.util.threadFactory +import dev.slne.surf.cloud.core.common.coroutines.BeforeStartTaskScope.unnamedTask import dev.slne.surf.surfapi.core.api.util.logger import kotlinx.coroutines.* import java.util.concurrent.Executors +import kotlin.coroutines.CoroutineContext abstract class BaseScope( dispatcher: CoroutineDispatcher, - private val name: String + name: String, + coroutineExceptionHandler: CoroutineExceptionHandler = CoroutineExceptionHandler { context, throwable -> + val coroutineName = context[CoroutineName]?.name ?: "Unnamed Coroutine" + log.atSevere() + .withCause(throwable) + .log("Unhandled exception in coroutine: $coroutineName") + } ) : CoroutineScope { companion object { + @JvmStatic + protected val log = logger() private val scopes = mutableObjectListOf() fun terminateAll() { scopes.forEach { it.cancel("Shutdown") } } } - protected val log = logger() - init { scopes.add(this) } - override val coroutineContext = dispatcher + CoroutineName(name) + SupervisorJob() + - CoroutineExceptionHandler { context, throwable -> - val coroutineName = context[CoroutineName]?.name ?: "Unnamed Coroutine" - log.atSevere() - .withCause(throwable) - .log("Unhandled exception in coroutine: $coroutineName") - } - + override val coroutineContext = + dispatcher + CoroutineName(name) + SupervisorJob() + coroutineExceptionHandler val context get() = coroutineContext } @@ -158,4 +160,40 @@ object CloudConnectionVerificationScope : BaseScope( object PunishmentCacheRefreshScope : BaseScope( dispatcher = Dispatchers.Default, name = "punishment-cache-refresh" +) + +object BeforeStartTaskScope : BaseScope( + dispatcher = Dispatchers.IO, + name = "before-start-task", + coroutineExceptionHandler = CoroutineExceptionHandler { context, throwable -> + val task= context[TaskName] ?: unnamedTask + log.atWarning() + .withCause(throwable) + .log("Unhandled exception in before start task: $task") + } +) { + @JvmStatic + private val unnamedTask = TaskName("Unnamed Task", Int.MAX_VALUE) + + data class TaskName( + val name: String, + val order: Int + ) : CoroutineContext.Element { + companion object Key : CoroutineContext.Key + override val key: CoroutineContext.Key<*> = Key + + override fun toString(): String { + return "$name (order: $order)" + } + } +} + +object SyncValueScope : BaseScope( + dispatcher = Dispatchers.Default, + name = "sync-value" +) + +object CommonObservableScope : BaseScope( + dispatcher = Dispatchers.Default, + name = "common-observable" ) \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/data/CloudPersistentData.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/data/CloudPersistentData.kt index 8d278885..2aeb5e75 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/data/CloudPersistentData.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/data/CloudPersistentData.kt @@ -1,6 +1,7 @@ package dev.slne.surf.cloud.core.common.data -import net.querz.nbt.tag.LongTag +import net.kyori.adventure.nbt.BinaryTagTypes +import net.kyori.adventure.nbt.LongBinaryTag object CloudPersistentData { @@ -8,8 +9,9 @@ object CloudPersistentData { var SERVER_ID by persistentData( "server_id", - { LongTag(it) }, - { asLong() }, + BinaryTagTypes.LONG, + { value() }, + { LongBinaryTag.longBinaryTag(it) }, SERVER_ID_NOT_SET ).nonNull() } diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/data/PersistentData.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/data/PersistentData.kt index d4d16911..f021584d 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/data/PersistentData.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/data/PersistentData.kt @@ -1,7 +1,7 @@ package dev.slne.surf.cloud.core.common.data -import net.querz.nbt.tag.Tag -import kotlin.reflect.KClass +import net.kyori.adventure.nbt.BinaryTag +import net.kyori.adventure.nbt.BinaryTagType interface PersistentData { fun value(): T? @@ -16,17 +16,17 @@ interface PersistentData { companion object { @JvmStatic - fun , D> data( + fun data( key: String, - type: Class, + type: BinaryTagType, toValue: (T) -> D, toTag: (D) -> T ): PersistentData = data(key, type, toValue, toTag, null) @JvmStatic - fun , D> data( + fun data( key: String, - type: Class, + type: BinaryTagType, toValue: (T) -> D, toTag: (D) -> T, defaultValue: D? @@ -59,18 +59,10 @@ interface NonNullPersistentData { } -fun , D> persistentData( +fun persistentData( key: String, - type: KClass, + type: BinaryTagType, toValue: T.() -> D, toTag: (D) -> T, defaultValue: D? = null -): PersistentData = PersistentDataImpl.data(key, type.java, toValue, toTag, defaultValue) - - -inline fun , D> persistentData( - key: String, - noinline toTag: (D) -> T, - noinline toValue: T.() -> D, - defaultValue: D? = null -) = persistentData(key, T::class, toValue, toTag, defaultValue) +): PersistentData = PersistentDataImpl.data(key, type, toValue, toTag, defaultValue) diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/data/PersistentDataImpl.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/data/PersistentDataImpl.kt index b322d808..3bfbc689 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/data/PersistentDataImpl.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/data/PersistentDataImpl.kt @@ -1,33 +1,36 @@ package dev.slne.surf.cloud.core.common.data -import dev.slne.surf.cloud.api.common.util.nbt.readCompoundTag -import dev.slne.surf.cloud.api.common.util.nbt.writeToPath import dev.slne.surf.cloud.core.common.coreCloudInstance -import net.querz.nbt.tag.CompoundTag -import net.querz.nbt.tag.Tag +import dev.slne.surf.surfapi.core.api.nbt.FastCompoundBinaryTag +import dev.slne.surf.surfapi.core.api.nbt.fast +import net.kyori.adventure.nbt.BinaryTag +import net.kyori.adventure.nbt.BinaryTagIO +import net.kyori.adventure.nbt.BinaryTagIO.Compression +import net.kyori.adventure.nbt.BinaryTagType +import net.kyori.adventure.nbt.CompoundBinaryTag import kotlin.io.path.createFile import kotlin.io.path.createParentDirectories import kotlin.io.path.div import kotlin.io.path.notExists -internal object PersistentDataImpl { +object PersistentDataImpl { private val file by lazy { (coreCloudInstance.dataFolder / "storage" / "data.dat").apply { if (notExists()) { createParentDirectories() createFile() - CompoundTag().writeToPath(this) + BinaryTagIO.writer().write(CompoundBinaryTag.empty(), this, Compression.GZIP) } } } - private val tag by lazy { file.readCompoundTag() } - private fun saveTag() = tag.writeToPath(file) + val tag by lazy { BinaryTagIO.unlimitedReader().read(file, Compression.GZIP).fast() } + private fun saveTag() = BinaryTagIO.writer().write(tag, file, Compression.GZIP) - fun , D> data( + fun data( key: String, - type: Class, + type: BinaryTagType, toValue: (T) -> D, toTag: (D) -> T, defaultValue: D? @@ -35,15 +38,26 @@ internal object PersistentDataImpl { return DataImpl(tag, key, toValue, toTag, type, defaultValue) } - private data class DataImpl, D>( - val tag: CompoundTag, + private data class DataImpl( + val tag: FastCompoundBinaryTag, val key: String, val toValue: (T) -> D, val toTag: (D) -> T, - val type: Class, + val type: BinaryTagType, val defaultValue: D? ) : PersistentData { - override fun value(): D? = tag.get(key, type)?.let { toValue(it) } ?: defaultValue + init { + tag.get("") + } + + override fun value(): D? { + val tag = tag.get(key) ?: return defaultValue + if (type.test(tag.type())) { + return toValue(tag as T) + } + throw IllegalStateException("Tag at key '$key' is not of type ${type}, but ${tag.type()}") + } + override fun setValue(value: D?) { if (value == null) { tag.remove(key) @@ -53,6 +67,6 @@ internal object PersistentDataImpl { saveTag() } - override operator fun contains(key: String): Boolean = tag.containsKey(key) + override operator fun contains(key: String): Boolean = tag.get(key) != null } } diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/event/CloudEventBusImpl.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/event/CloudEventBusImpl.kt index dff923c2..326f8e77 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/event/CloudEventBusImpl.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/event/CloudEventBusImpl.kt @@ -11,24 +11,18 @@ import dev.slne.surf.cloud.api.common.util.mutableObjectListOf import dev.slne.surf.cloud.api.common.util.synchronize import dev.slne.surf.cloud.core.common.coroutines.CloudEventBusScope import dev.slne.surf.surfapi.core.api.util.findAnnotation -import io.ktor.client.plugins.cache.storage.FileStorage import it.unimi.dsi.fastutil.objects.ObjectList import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import net.bytebuddy.ByteBuddy import net.bytebuddy.description.modifier.FieldManifestation import net.bytebuddy.description.modifier.Visibility -import net.bytebuddy.description.type.TypeDescription import net.bytebuddy.dynamic.loading.ClassLoadingStrategy import net.bytebuddy.implementation.FieldAccessor import net.bytebuddy.implementation.MethodCall import net.bytebuddy.implementation.bytecode.assign.Assigner -import net.bytebuddy.implementation.bytecode.assign.TypeCasting -import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess import net.bytebuddy.matcher.ElementMatchers -import net.bytebuddy.utility.CompoundList import org.springframework.core.ResolvableType -import org.springframework.core.convert.TypeDescriptor import org.springframework.expression.spel.standard.SpelExpressionParser import org.springframework.expression.spel.support.StandardEvaluationContext import java.lang.reflect.Method @@ -125,14 +119,19 @@ class CloudEventBusImpl : CloudEventBus { .subclass(EventListenerInvoker::class.java) .implement(GeneratedInvoker::class.java) .defineField("owner", instance.javaClass, Visibility.PRIVATE, FieldManifestation.FINAL) + .defineMethod("getOwner", Any::class.java, Visibility.PUBLIC) + .intercept(FieldAccessor.ofField("owner")) .defineConstructor(Visibility.PUBLIC).withParameters(instance.javaClass) - .intercept(MethodCall.invoke(Object::class.java.getConstructor()) - .andThen(FieldAccessor.ofField("owner").setsArgumentAt(0))) + .intercept( + MethodCall.invoke(Object::class.java.getConstructor()) + .andThen(FieldAccessor.ofField("owner").setsArgumentAt(0)) + ) .method(ElementMatchers.named("invoke")) - .intercept(MethodCall.invoke(method) - .onField("owner") - .withArgument(0) - .withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC) + .intercept( + MethodCall.invoke(method) + .onField("owner") + .withArgument(0) + .withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC) ) .make() .load(instance.javaClass.classLoader, ClassLoadingStrategy.Default.INJECTION) @@ -143,7 +142,10 @@ class CloudEventBusImpl : CloudEventBus { } private class ReflectionInvoker(val instance: Any, val method: Method) : EventListenerInvoker { - init { method.isAccessible = true } + init { + method.isAccessible = true + } + override suspend fun invoke(event: CloudEvent) { CloudEventBusScope.launch { val kfn = method.kotlinFunction ?: error("Not a Kotlin function") @@ -151,7 +153,10 @@ class CloudEventBusImpl : CloudEventBus { }.join() } } - internal interface GeneratedInvoker { val owner: Any } + + internal interface GeneratedInvoker { + val owner: Any + } } val cloudEventBusImpl = CloudEventBus.instance as CloudEventBusImpl \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/event/CloudEventListenerBeanPostProcessor.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/event/CloudEventListenerBeanPostProcessor.kt index 3a6c96a4..8d1f0679 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/event/CloudEventListenerBeanPostProcessor.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/event/CloudEventListenerBeanPostProcessor.kt @@ -1,17 +1,16 @@ package dev.slne.surf.cloud.core.common.event import dev.slne.surf.cloud.api.common.event.CloudEventHandler -import dev.slne.surf.cloud.api.common.util.isAnnotated -import dev.slne.surf.cloud.api.common.util.isCandidateFor -import dev.slne.surf.cloud.api.common.util.mutableObject2ObjectMapOf -import dev.slne.surf.cloud.api.common.util.selectFunctions -import dev.slne.surf.cloud.api.common.util.ultimateTargetClass +import dev.slne.surf.cloud.api.common.util.* +import org.springframework.beans.factory.config.BeanDefinition import org.springframework.beans.factory.config.BeanPostProcessor import org.springframework.context.SmartLifecycle +import org.springframework.context.annotation.Role import org.springframework.stereotype.Component import java.lang.reflect.Method @Component +@Role(BeanDefinition.ROLE_INFRASTRUCTURE) class CloudEventListenerBeanPostProcessor : BeanPostProcessor, SmartLifecycle { private val watched = mutableObject2ObjectMapOf>() diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/messages/MessageManager.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/messages/MessageManager.kt index 43f85f86..0bc120c5 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/messages/MessageManager.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/messages/MessageManager.kt @@ -37,7 +37,7 @@ object MessageManager { // TODO: Add more messages this, "BEIM VERBINDUNGSAUFBAU IST EIN FEHLER AUFGETRETEN", { - error("Beim versuch eine Verbindung aufzubauen ist") + error("Beim Versuch, eine Verbindung aufzubauen, ist") appendNewline() error("ein unbekannter Fehler aufgetreten.") }, @@ -48,9 +48,9 @@ object MessageManager { // TODO: Add more messages val loginTimedOut = buildText { CommonComponents.renderDisconnectMessage( this, - "VERBINDUNG ZUR SERVER-INSTANCE WURDE GETRENNT", + "VERBINDUNG ZUR SERVER-INSTANZ WURDE GETRENNT", { - error("Die Verbindung zur Server-Instance wurde") + error("Die Verbindung zur Server-Instanz wurde") appendNewline() error("unerwartet getrennt.") }, @@ -58,6 +58,19 @@ object MessageManager { // TODO: Add more messages ) } + val noServersAvailableToJoin = buildText { + CommonComponents.renderDisconnectMessage( + this, + "KEINE SERVER ZUM BETRETEN VERFÜGBAR", + { + error("Es sind aktuell keine Server verfügbar,") + appendNewline() + error("denen du beitreten kannst.") + }, + true + ) + } + fun formatZonedDateTime(time: ZonedDateTime?) = buildText { if (time == null) { variableValue("N/A") @@ -138,7 +151,7 @@ object MessageManager { // TODO: Add more messages appendNewline(3) if (!punishment.securityBan) { - spacer("Du denkst dies ist eine Fehlentscheidung?") + spacer("Du denkst, dies ist eine Fehlentscheidung?") } appendNewline { spacer("Kontaktiere den Support in unserem Discord") @@ -199,7 +212,7 @@ object MessageManager { // TODO: Add more messages } object Mute { - val voiceMutedActionbar = buildText { error("Du bist vom Voicechat ausgeschlossen!") } + val voiceMutedActionbar = buildText { error("Du bist vom Voice-Chat ausgeschlossen!") } operator fun invoke(punishment: PunishmentMute) = MuteComponentBuilder(punishment) diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/CommonNettyClientImpl.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/CommonNettyClientImpl.kt index b6196f0c..1d82b5d9 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/CommonNettyClientImpl.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/CommonNettyClientImpl.kt @@ -8,6 +8,7 @@ import dev.slne.surf.cloud.api.common.util.mutableObject2ObjectMapOf import dev.slne.surf.cloud.api.common.util.synchronize import dev.slne.surf.cloud.core.common.netty.network.ConnectionImpl import kotlinx.coroutines.CompletableDeferred +import java.net.InetSocketAddress import kotlin.time.Duration abstract class CommonNettyClientImpl( @@ -41,6 +42,7 @@ abstract class CommonNettyClientImpl( override val connection get() = _connection ?: error("connection not yet set") + abstract val playAddress: InetSocketAddress val displayName get() = "${serverCategory}/${serverId} $serverName (${_connection?.getLoggableAddress()})" override fun fireAndForget(packet: NettyPacket) { @@ -77,7 +79,7 @@ abstract class CommonNettyClientImpl( abstract fun broadcast(packets: List) - protected fun initConnection(connection: ConnectionImpl) { + fun initConnection(connection: ConnectionImpl) { check(_connection == null) { "Connection already set" } _connection = connection } diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/ConnectionImpl.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/ConnectionImpl.kt index 4f625b66..7789754e 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/ConnectionImpl.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/ConnectionImpl.kt @@ -22,6 +22,7 @@ import dev.slne.surf.cloud.core.common.netty.network.protocol.initialize.* import dev.slne.surf.cloud.core.common.netty.network.protocol.login.* import dev.slne.surf.cloud.core.common.netty.network.protocol.prerunning.* import dev.slne.surf.cloud.core.common.netty.network.protocol.running.* +import dev.slne.surf.cloud.core.common.netty.network.protocol.synchronizing.* import dev.slne.surf.surfapi.core.api.util.logger import io.netty.bootstrap.Bootstrap import io.netty.channel.* @@ -31,11 +32,13 @@ import io.netty.channel.epoll.EpollSocketChannel import io.netty.channel.nio.NioIoHandler import io.netty.channel.socket.SocketChannel import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.DecoderException import io.netty.handler.codec.EncoderException import io.netty.handler.codec.compression.ZstdDecoder import io.netty.handler.codec.compression.ZstdEncoder import io.netty.handler.flow.FlowControlHandler import io.netty.handler.logging.LoggingHandler +import io.netty.handler.ssl.NotSslRecordException import io.netty.handler.timeout.ReadTimeoutHandler import io.netty.handler.timeout.TimeoutException import io.netty.util.AttributeKey @@ -94,6 +97,8 @@ class ConnectionImpl( override var averageSentPackets = 0f private set + override var latency: Int = 0 + private var handlingFault = false /** @@ -137,10 +142,10 @@ class ConnectionImpl( } override fun exceptionCaught(ctx: ChannelHandlerContext, e: Throwable?) { -// if (e is NotSslRecordException) { -// ctx.close() -// return -// } + if (e is DecoderException && e.cause is NotSslRecordException) { + ctx.close() + return + } log.atInfo().withCause(e).log("Exception caught") // TODO: remove this debug line var throwable = e @@ -270,7 +275,7 @@ class ConnectionImpl( } is ServerPreRunningPacketListener -> when (msg) { - is ServerboundReadyToRunPacket -> listener.handleReadyToRun(msg) + is ServerboundProceedToSynchronizingAcknowledgedPacket -> listener.handleReadyToRun(msg) is ServerboundPreRunningAcknowledgedPacket -> listener.handlePreRunningAcknowledged( msg ) @@ -280,6 +285,15 @@ class ConnectionImpl( else -> error("Unexpected packet $msg") } + is ServerSynchronizingPacketListener -> when (msg) { + is FinishSynchronizingPacket -> listener.handleFinishSynchronizing(msg) + is ServerboundSynchronizeFinishAcknowledgedPacket -> listener.handleSynchronizeFinishAcknowledged(msg) + is SyncValueChangePacket -> listener.handleSyncValueChange(msg) + is SyncSetDeltaPacket -> listener.handleSyncSetDelta(msg) + + else -> listener.handlePacket(msg) + } + is RunningServerPacketListener -> when (msg) { is PlayerConnectToServerPacket -> listener.handlePlayerConnectToServer( msg @@ -395,6 +409,8 @@ class ConnectionImpl( is RequestPlayerPermissionPacket -> listener.handleRequestPlayerPermission( msg ) + is SyncValueChangePacket -> listener.handleSyncValueChange(msg) + is SyncSetDeltaPacket -> listener.handleSyncSetDelta(msg) else -> listener.handlePacket(msg) // handle other packets } @@ -434,13 +450,25 @@ class ConnectionImpl( msg ) - is ClientboundReadyToRunPacket -> listener.handleReadyToRun(msg) + is ClientboundProceedToSynchronizingPacket -> listener.handleProceedToSynchronizing(msg) else -> error("Unexpected packet $msg") } + is ClientSynchronizingPacketListener -> when (msg) { + is ClientboundSynchronizeFinishPacket -> listener.handleSynchronizeFinish(msg) + is SyncValueChangePacket -> listener.handleSyncValueChange(msg) + is ClientboundBatchSyncValuePacket -> listener.handleBatchSyncValue(msg) + is ClientboundBatchSyncSetPacket -> listener.handleBatchSyncSet(msg) + is ClientboundBatchUpdateServer -> listener.handleBatchUpdateServer(msg) + is SyncSetDeltaPacket -> listener.handleSyncSetDelta(msg) + is ClientboundSetVelocitySecretPacket -> listener.handleSetVelocitySecret(msg) + + else -> listener.handlePacket(msg) + } + is RunningClientPacketListener -> when (msg) { - is PlayerConnectToServerPacket -> listener.handlePlayerConnectToServer( + is PlayerConnectedToServerPacket -> listener.handlePlayerConnectedToServer( msg ) @@ -521,7 +549,6 @@ class ConnectionImpl( ) is ClientboundTriggerShutdownPacket -> listener.handleTriggerShutdown(msg) - is ClientboundBatchUpdateServer -> listener.handleBatchUpdateServer(msg) is UpdateAFKStatePacket -> listener.handleUpdateAFKState(msg) is ClientboundRunPrePlayerJoinTasksPacket -> listener.handleRunPlayerPreJoinTasks( msg @@ -538,6 +565,9 @@ class ConnectionImpl( is RequestPlayerPermissionPacket -> listener.handleRequestPlayerPermission( msg ) + is SyncValueChangePacket -> listener.handleSyncValueChange(msg) + is SyncSetDeltaPacket -> listener.handleSyncSetDelta(msg) + is ClientboundSetVelocitySecretPacket -> listener.handleSetVelocitySecret(msg) else -> listener.handlePacket(msg) } @@ -892,7 +922,7 @@ class ConnectionImpl( disconnect(DisconnectionDetails(reason)) } - fun disconnect(reason: DisconnectionDetails) { + fun disconnect(reason: DisconnectionDetails): ChannelFuture? { preparing = false clearPacketQueue() @@ -900,13 +930,16 @@ class ConnectionImpl( if (channel == null) { this.delayedDisconnect = reason - return + return null } if (connected) { - channel.close() + val future = channel.close() this.disconnectionDetails = reason + return future } + + return null } fun clearPacketQueue() { @@ -956,6 +989,24 @@ class ConnectionImpl( fun getLoggableAddress(logIps: Boolean) = if (_address == null) "local" else (if (logIps) _address.toString() else "IP hidden") + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is ConnectionImpl) return false + + if (_channel != other._channel) return false + if (receiving != other.receiving) return false + if (_address != other._address) return false + + return true + } + + override fun hashCode(): Int { + var result = receiving.hashCode() + result = 31 * result + (_channel?.hashCode() ?: 0) + result = 31 * result + (_address?.hashCode() ?: 0) + return result + } + private object Util { fun canSendImmediate(connection: ConnectionImpl, packet: NettyPacket): Boolean { diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/ProtocolInfo.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/ProtocolInfo.kt index d735a947..33a0b0b7 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/ProtocolInfo.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/ProtocolInfo.kt @@ -24,7 +24,7 @@ interface ProtocolInfo { interface Mutable: Unbound { fun

addPacket( id: Class

, - codec: StreamCodec + codec: StreamCodec ): Mutable fun freeze(): Unbound diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/ProtocolInfoBuilder.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/ProtocolInfoBuilder.kt index 0aa7a5f0..3b6b9a83 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/ProtocolInfoBuilder.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/ProtocolInfoBuilder.kt @@ -29,7 +29,7 @@ class ProtocolInfoBuilder( fun

addPacket( id: Class

, - codec: StreamCodec + codec: StreamCodec ) = apply { codecs.add(CodecEntry(id, codec)) } inline fun addPacket(codec: StreamCodec) = @@ -89,7 +89,7 @@ class ProtocolInfoBuilder( override fun

addPacket( id: Class

, - codec: StreamCodec + codec: StreamCodec ) = apply { check(!frozen) { "Cannot add packets after freezing" } this@ProtocolInfoBuilder.addPacket(id, codec) diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/common/ClientboundPongResponsePacket.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/common/ClientboundPongResponsePacket.kt index ce9ead41..87ee9655 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/common/ClientboundPongResponsePacket.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/common/ClientboundPongResponsePacket.kt @@ -8,8 +8,14 @@ import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket import dev.slne.surf.cloud.api.common.netty.packet.packetCodec import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf -@SurfNettyPacket(DefaultIds.CLIENTBOUND_PING_REQUEST_RESPONSE_PACKET, PacketFlow.CLIENTBOUND, ConnectionProtocol.RUNNING, ConnectionProtocol.PRE_RUNNING) -class ClientboundPongResponsePacket: NettyPacket { +@SurfNettyPacket( + DefaultIds.CLIENTBOUND_PING_REQUEST_RESPONSE_PACKET, + PacketFlow.CLIENTBOUND, + ConnectionProtocol.RUNNING, + ConnectionProtocol.PRE_RUNNING, + ConnectionProtocol.SYNCHRONIZING, +) +class ClientboundPongResponsePacket : NettyPacket { companion object { val STREAM_CODEC = diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/common/ClientboundSetVelocitySecretPacket.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/common/ClientboundSetVelocitySecretPacket.kt new file mode 100644 index 00000000..364f5abd --- /dev/null +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/common/ClientboundSetVelocitySecretPacket.kt @@ -0,0 +1,14 @@ +package dev.slne.surf.cloud.core.common.netty.network.protocol.common + +import dev.slne.surf.cloud.api.common.meta.SurfNettyPacket +import dev.slne.surf.cloud.api.common.netty.network.protocol.PacketFlow +import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket +import kotlinx.serialization.Serializable + +@SurfNettyPacket("cloud:clientbound:set_velocity_secret", PacketFlow.CLIENTBOUND) +@Serializable +class ClientboundSetVelocitySecretPacket(val secret: ByteArray): NettyPacket() { + override fun toString(): String { + return "ClientboundSetVelocitySecretPacket(secret=***)" + } +} \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/common/KeepAliveHandler.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/common/KeepAliveHandler.kt index 4f36ecdd..ecf1b2a6 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/common/KeepAliveHandler.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/common/KeepAliveHandler.kt @@ -23,9 +23,6 @@ class KeepAliveHandler( private var keepAlivePending = false private var keepAliveChallenge = KeepAliveTime(0) - var latency = 0 - private set - suspend fun keepConnectionAlive() { val currentTime = KeepAliveTime.now() val elapsedTime = currentTime - keepAliveTime @@ -55,7 +52,7 @@ class KeepAliveHandler( if (keepAlivePending && keepAliveId != null && keepAliveId == keepAliveChallenge.time) { val elapsedTime = KeepAliveTime.now() - keepAliveTime - this.latency = ((latency * 3 + elapsedTime) / 4).toInt() + connection.latency = ((connection.latency * 3 + elapsedTime) / 4).toInt() this.keepAlivePending = false } else { disconnect(DisconnectReason.TIMEOUT) diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/common/KeepAlivePacket.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/common/KeepAlivePacket.kt index 6771f243..b792e40f 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/common/KeepAlivePacket.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/common/KeepAlivePacket.kt @@ -10,7 +10,8 @@ import kotlinx.serialization.Serializable "cloud:bidirectional:keep_alive", PacketFlow.BIDIRECTIONAL, ConnectionProtocol.RUNNING, - ConnectionProtocol.PRE_RUNNING + ConnectionProtocol.PRE_RUNNING, + ConnectionProtocol.SYNCHRONIZING, ) @Serializable class KeepAlivePacket(val keepAliveId: Long) : LongResponsePacket() \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/common/ServerboundPingRequestPacket.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/common/ServerboundPingRequestPacket.kt index 380dac69..00cf404c 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/common/ServerboundPingRequestPacket.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/common/ServerboundPingRequestPacket.kt @@ -8,8 +8,14 @@ import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket import dev.slne.surf.cloud.api.common.netty.packet.packetCodec import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf -@SurfNettyPacket(DefaultIds.SERVERBOUND_PING_REQUEST_PACKET, PacketFlow.SERVERBOUND, ConnectionProtocol.RUNNING, ConnectionProtocol.PRE_RUNNING) -class ServerboundPingRequestPacket: NettyPacket { +@SurfNettyPacket( + DefaultIds.SERVERBOUND_PING_REQUEST_PACKET, + PacketFlow.SERVERBOUND, + ConnectionProtocol.RUNNING, + ConnectionProtocol.PRE_RUNNING, + ConnectionProtocol.SYNCHRONIZING +) +class ServerboundPingRequestPacket : NettyPacket { companion object { val STREAM_CODEC = packetCodec(ServerboundPingRequestPacket::write, ::ServerboundPingRequestPacket) diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/login/ServerboundLoginStartPacket.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/login/ServerboundLoginStartPacket.kt index 850280f8..23ae5ac8 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/login/ServerboundLoginStartPacket.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/login/ServerboundLoginStartPacket.kt @@ -11,7 +11,11 @@ import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf /** * This packet is sent by the client to the server to start the login process. */ -@SurfNettyPacket(DefaultIds.SERVERBOUND_LOGIN_START_PACKET, PacketFlow.SERVERBOUND, ConnectionProtocol.LOGIN) // aka HelloPacket +@SurfNettyPacket( + DefaultIds.SERVERBOUND_LOGIN_START_PACKET, + PacketFlow.SERVERBOUND, + ConnectionProtocol.LOGIN +) // aka HelloPacket class ServerboundLoginStartPacket : NettyPacket { companion object { @@ -24,12 +28,20 @@ class ServerboundLoginStartPacket : NettyPacket { val serverCategory: String val serverName: String val proxy: Boolean + val lobby: Boolean - constructor(serverId: Long, serverCategory: String, serverName: String, proxy: Boolean) { + constructor( + serverId: Long, + serverCategory: String, + serverName: String, + proxy: Boolean, + lobby: Boolean, + ) { this.serverId = serverId this.serverCategory = serverCategory this.serverName = serverName this.proxy = proxy + this.lobby = lobby } private constructor(buffer: SurfByteBuf) { @@ -37,6 +49,7 @@ class ServerboundLoginStartPacket : NettyPacket { serverCategory = buffer.readUtf() serverName = buffer.readUtf() proxy = buffer.readBoolean() + lobby = buffer.readBoolean() } private fun write(buf: SurfByteBuf) { @@ -44,5 +57,6 @@ class ServerboundLoginStartPacket : NettyPacket { buf.writeUtf(serverCategory) buf.writeUtf(serverName) buf.writeBoolean(proxy) + buf.writeBoolean(lobby) } } \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/prerunning/ClientPreRunningPacketListener.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/prerunning/ClientPreRunningPacketListener.kt index 27af5311..9f538752 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/prerunning/ClientPreRunningPacketListener.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/prerunning/ClientPreRunningPacketListener.kt @@ -8,5 +8,5 @@ interface ClientPreRunningPacketListener : ClientCommonPacketListener { suspend fun handlePreRunningFinished(packet: ClientboundPreRunningFinishedPacket) - suspend fun handleReadyToRun(packet: ClientboundReadyToRunPacket) + suspend fun handleProceedToSynchronizing(packet: ClientboundProceedToSynchronizingPacket) } \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/prerunning/ClientboundReadyToRunPacket.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/prerunning/ClientboundProceedToSynchronizingPacket.kt similarity index 91% rename from surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/prerunning/ClientboundReadyToRunPacket.kt rename to surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/prerunning/ClientboundProceedToSynchronizingPacket.kt index c8589abf..3edae1b3 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/prerunning/ClientboundReadyToRunPacket.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/prerunning/ClientboundProceedToSynchronizingPacket.kt @@ -12,7 +12,7 @@ import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket PacketFlow.CLIENTBOUND, ConnectionProtocol.PRE_RUNNING ) -object ClientboundReadyToRunPacket: NettyPacket() { +object ClientboundProceedToSynchronizingPacket: NettyPacket() { val STREAM_CODEC = streamCodecUnitSimple(this) override val terminal = true } \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/prerunning/PreRunningProtocols.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/prerunning/PreRunningProtocols.kt index 70f612f3..b59469fd 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/prerunning/PreRunningProtocols.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/prerunning/PreRunningProtocols.kt @@ -18,7 +18,7 @@ object PreRunningProtocols { .addPacket(LongResponse.STREAM_CODEC) .addPacket(ClientboundPongResponsePacket.STREAM_CODEC) .addPacket(ClientboundPreRunningFinishedPacket.STREAM_CODEC) - .addPacket(ClientboundReadyToRunPacket.STREAM_CODEC) + .addPacket(ClientboundProceedToSynchronizingPacket.STREAM_CODEC) } val CLIENTBOUND = CLIENTBOUND_TEMPLATE.bind(::SurfByteBuf) @@ -32,7 +32,7 @@ object PreRunningProtocols { .addPacket(LongResponse.STREAM_CODEC) .addPacket(ServerboundPingRequestPacket.STREAM_CODEC) .addPacket(ServerboundPreRunningAcknowledgedPacket.STREAM_CODEC) - .addPacket(ServerboundReadyToRunPacket.STREAM_CODEC) + .addPacket(ServerboundProceedToSynchronizingAcknowledgedPacket::class.createCodec()) .addPacket(ServerboundRequestContinuation.STREAM_CODEC) } diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/prerunning/ServerPreRunningPacketListener.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/prerunning/ServerPreRunningPacketListener.kt index c9c77be6..75a3fcb8 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/prerunning/ServerPreRunningPacketListener.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/prerunning/ServerPreRunningPacketListener.kt @@ -8,7 +8,7 @@ interface ServerPreRunningPacketListener : ServerCommonPacketListener { fun handleRequestContinuation(packet: ServerboundRequestContinuation) - suspend fun handleReadyToRun(packet: ServerboundReadyToRunPacket) + suspend fun handleReadyToRun(packet: ServerboundProceedToSynchronizingAcknowledgedPacket) suspend fun handlePreRunningAcknowledged(packet: ServerboundPreRunningAcknowledgedPacket) } \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/prerunning/ServerboundReadyToRunPacket.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/prerunning/ServerboundProceedToSynchronizingAcknowledgedPacket.kt similarity index 68% rename from surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/prerunning/ServerboundReadyToRunPacket.kt rename to surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/prerunning/ServerboundProceedToSynchronizingAcknowledgedPacket.kt index a4708844..91acbcdc 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/prerunning/ServerboundReadyToRunPacket.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/prerunning/ServerboundProceedToSynchronizingAcknowledgedPacket.kt @@ -3,16 +3,19 @@ package dev.slne.surf.cloud.core.common.netty.network.protocol.prerunning import dev.slne.surf.cloud.api.common.meta.DefaultIds import dev.slne.surf.cloud.api.common.meta.SurfNettyPacket import dev.slne.surf.cloud.api.common.netty.network.ConnectionProtocol -import dev.slne.surf.cloud.api.common.netty.network.codec.streamCodecUnitSimple import dev.slne.surf.cloud.api.common.netty.network.protocol.PacketFlow import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable +import java.net.InetSocketAddress @SurfNettyPacket( DefaultIds.SERVERBOUND_READY_TO_RUN_PACKET, PacketFlow.SERVERBOUND, ConnectionProtocol.PRE_RUNNING ) -object ServerboundReadyToRunPacket : NettyPacket() { - val STREAM_CODEC = streamCodecUnitSimple(this) +@Serializable +class ServerboundProceedToSynchronizingAcknowledgedPacket(val playAddress: @Contextual InetSocketAddress) : + NettyPacket() { override val terminal = true } \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/ClientboundBatchUpdateServer.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/ClientboundBatchUpdateServer.kt index f760a1b8..793c849a 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/ClientboundBatchUpdateServer.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/ClientboundBatchUpdateServer.kt @@ -2,14 +2,21 @@ package dev.slne.surf.cloud.core.common.netty.network.protocol.running import dev.slne.surf.cloud.api.common.meta.DefaultIds import dev.slne.surf.cloud.api.common.meta.SurfNettyPacket +import dev.slne.surf.cloud.api.common.netty.network.ConnectionProtocol import dev.slne.surf.cloud.api.common.netty.network.protocol.PacketFlow import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket import dev.slne.surf.cloud.api.common.netty.packet.packetCodec import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf +import dev.slne.surf.cloud.api.common.server.CloudServer import dev.slne.surf.cloud.api.common.server.CommonCloudServer import dev.slne.surf.cloud.api.common.server.ProxyCloudServer +import java.net.InetSocketAddress -@SurfNettyPacket(DefaultIds.CLIENTBOUND_BATCH_UPDATE_SERVER, PacketFlow.CLIENTBOUND) +@SurfNettyPacket( + DefaultIds.CLIENTBOUND_BATCH_UPDATE_SERVER, + PacketFlow.CLIENTBOUND, + ConnectionProtocol.SYNCHRONIZING +) class ClientboundBatchUpdateServer( val servers: List ) : NettyPacket() { @@ -19,16 +26,25 @@ class ClientboundBatchUpdateServer( packetCodec(ClientboundBatchUpdateServer::write, ::ClientboundBatchUpdateServer) } - constructor(servers: Iterable) : this(servers.map { - UpdateServerData(it.uid, it is ProxyCloudServer, it.group, it.name) + constructor(servers: Iterable) : this(servers.map { server -> + UpdateServerData( + server.uid, + server is ProxyCloudServer, + (server as? CloudServer)?.lobby ?: false, + server.group, + server.name, + server.playAddress + ) }) private constructor(buf: SurfByteBuf) : this(buf.readList { UpdateServerData( buf.readVarLong(), buf.readBoolean(), + buf.readBoolean(), + buf.readUtf(), buf.readUtf(), - buf.readUtf() + buf.readInetSocketAddress() ) }) @@ -36,8 +52,10 @@ class ClientboundBatchUpdateServer( buf.writeCollection(servers) { buf, data -> buf.writeVarLong(data.serverId) buf.writeBoolean(data.proxy) + buf.writeBoolean(data.lobby) buf.writeUtf(data.group) buf.writeUtf(data.name) + buf.writeInetSocketAddress(data.playAddress) } } } @@ -45,6 +63,8 @@ class ClientboundBatchUpdateServer( data class UpdateServerData( val serverId: Long, val proxy: Boolean, + val lobby: Boolean, val group: String, - val name: String + val name: String, + val playAddress: InetSocketAddress, ) \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/ClientboundPlayerPersistentDataContainerResponse.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/ClientboundPlayerPersistentDataContainerResponse.kt index 5fdf6966..3821cdf6 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/ClientboundPlayerPersistentDataContainerResponse.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/ClientboundPlayerPersistentDataContainerResponse.kt @@ -6,7 +6,7 @@ import dev.slne.surf.cloud.api.common.netty.network.protocol.PacketFlow import dev.slne.surf.cloud.api.common.netty.packet.ResponseNettyPacket import dev.slne.surf.cloud.api.common.netty.packet.packetCodec import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf -import net.querz.nbt.tag.CompoundTag +import net.kyori.adventure.nbt.CompoundBinaryTag @SurfNettyPacket( DefaultIds.CLIENTBOUND_PLAYER_PERSISTENT_DATA_CONTAINER_RESPONSE, @@ -21,9 +21,9 @@ class ClientboundPlayerPersistentDataContainerResponse : ResponseNettyPacket { } val verificationId: Int - val nbt: CompoundTag + val nbt: CompoundBinaryTag - constructor(verificationId: Int, nbt: CompoundTag) { + constructor(verificationId: Int, nbt: CompoundBinaryTag) { this.verificationId = verificationId this.nbt = nbt } diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/ClientboundRegisterServerPacket.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/ClientboundRegisterServerPacket.kt index 60c67151..f3ccbb4b 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/ClientboundRegisterServerPacket.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/ClientboundRegisterServerPacket.kt @@ -6,37 +6,54 @@ import dev.slne.surf.cloud.api.common.netty.network.protocol.PacketFlow import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket import dev.slne.surf.cloud.api.common.netty.packet.packetCodec import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf +import java.net.InetSocketAddress @SurfNettyPacket(DefaultIds.CLIENTBOUND_REGISTER_SERVER_PACKET, PacketFlow.CLIENTBOUND) -class ClientboundRegisterServerPacket: NettyPacket { +class ClientboundRegisterServerPacket : NettyPacket { companion object { - val STREAM_CODEC = packetCodec(ClientboundRegisterServerPacket::write, ::ClientboundRegisterServerPacket) + val STREAM_CODEC = + packetCodec(ClientboundRegisterServerPacket::write, ::ClientboundRegisterServerPacket) } val serverId: Long val proxy: Boolean + val lobby: Boolean val group: String val name: String + val playAddress: InetSocketAddress - constructor(serverId: Long, proxy: Boolean, group: String, name: String) { + constructor( + serverId: Long, + proxy: Boolean, + lobby: Boolean, + group: String, + name: String, + address: InetSocketAddress, + ) { this.serverId = serverId this.proxy = proxy + this.lobby = lobby this.group = group this.name = name + this.playAddress = address } private constructor(buf: SurfByteBuf) { serverId = buf.readVarLong() proxy = buf.readBoolean() + lobby = buf.readBoolean() group = buf.readUtf() name = buf.readUtf() + playAddress = buf.readInetSocketAddress() } private fun write(buf: SurfByteBuf) { buf.writeVarLong(serverId) buf.writeBoolean(proxy) + buf.writeBoolean(lobby) buf.writeUtf(group) buf.writeUtf(name) + buf.writeInetSocketAddress(playAddress) } } \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/ClientboundRunPrePlayerJoinTasksPacket.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/ClientboundRunPrePlayerJoinTasksPacket.kt index dd468437..ded22a3a 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/ClientboundRunPrePlayerJoinTasksPacket.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/ClientboundRunPrePlayerJoinTasksPacket.kt @@ -4,7 +4,7 @@ import dev.slne.surf.cloud.api.common.meta.SurfNettyPacket import dev.slne.surf.cloud.api.common.netty.network.protocol.PacketFlow import dev.slne.surf.cloud.api.common.netty.packet.RespondingNettyPacket import dev.slne.surf.cloud.api.common.netty.packet.ResponseNettyPacket -import dev.slne.surf.cloud.core.common.player.task.PrePlayerJoinTask +import dev.slne.surf.cloud.api.common.player.task.PrePlayerJoinTask import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import java.util.* diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/PlayerConnectToServerPacket.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/PlayerConnectToServerPacket.kt index 5b633a6b..1ad7bf09 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/PlayerConnectToServerPacket.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/PlayerConnectToServerPacket.kt @@ -6,9 +6,7 @@ import dev.slne.surf.cloud.api.common.netty.network.protocol.PacketFlow import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket import dev.slne.surf.cloud.api.common.netty.packet.RespondingNettyPacket import dev.slne.surf.cloud.api.common.netty.packet.ResponseNettyPacket -import dev.slne.surf.cloud.api.common.netty.packet.packetCodec -import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf -import dev.slne.surf.cloud.core.common.player.task.PrePlayerJoinTask +import dev.slne.surf.cloud.api.common.player.task.PrePlayerJoinTask import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import java.net.Inet4Address @@ -24,7 +22,7 @@ import java.util.* * @param serverUid The uid of the server the player is connecting to * @param proxy If the server is a proxy */ -@SurfNettyPacket(DefaultIds.PLAYER_CONNECT_TO_SERVER_PACKET, PacketFlow.BIDIRECTIONAL) +@SurfNettyPacket(DefaultIds.PLAYER_CONNECT_TO_SERVER_PACKET, PacketFlow.SERVERBOUND) @Serializable data class PlayerConnectToServerPacket( val uuid: @Contextual UUID, @@ -34,6 +32,16 @@ data class PlayerConnectToServerPacket( val playerIp: @Contextual Inet4Address, ) : RespondingNettyPacket() +@SurfNettyPacket(DefaultIds.PLAYER_CONNECTED_TO_SERVER_PACKET, PacketFlow.CLIENTBOUND) +@Serializable +data class PlayerConnectedToServerPacket( + val uuid: @Contextual UUID, + val name: String, + val serverUid: Long, + val proxy: Boolean, + val playerIp: @Contextual Inet4Address, +) : NettyPacket() + @SurfNettyPacket("cloud:response:connect_to_server", PacketFlow.BIDIRECTIONAL) @Serializable class PlayerConnectToServerResponsePacket(val result: PrePlayerJoinTask.Result) : ResponseNettyPacket() \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/RunningClientPacketListener.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/RunningClientPacketListener.kt index dbb0306e..3011b165 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/RunningClientPacketListener.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/RunningClientPacketListener.kt @@ -3,11 +3,12 @@ package dev.slne.surf.cloud.core.common.netty.network.protocol.running import dev.slne.surf.cloud.api.common.netty.network.ConnectionProtocol import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket import dev.slne.surf.cloud.core.common.netty.network.protocol.common.ClientCommonPacketListener +import dev.slne.surf.cloud.core.common.netty.network.protocol.common.ClientboundSetVelocitySecretPacket interface RunningClientPacketListener : ClientCommonPacketListener { override val protocol get() = ConnectionProtocol.RUNNING - suspend fun handlePlayerConnectToServer(packet: PlayerConnectToServerPacket) + suspend fun handlePlayerConnectedToServer(packet: PlayerConnectedToServerPacket) suspend fun handlePlayerDisconnectFromServer(packet: PlayerDisconnectFromServerPacket) @@ -73,8 +74,6 @@ interface RunningClientPacketListener : ClientCommonPacketListener { fun handleTriggerShutdown(packet: ClientboundTriggerShutdownPacket) - suspend fun handleBatchUpdateServer(packet: ClientboundBatchUpdateServer) - fun handleUpdateAFKState(packet: UpdateAFKStatePacket) suspend fun handleRunPlayerPreJoinTasks(packet: ClientboundRunPrePlayerJoinTasksPacket) @@ -85,5 +84,11 @@ interface RunningClientPacketListener : ClientCommonPacketListener { suspend fun handleRequestPlayerPermission(packet: RequestPlayerPermissionPacket) + fun handleSyncValueChange(packet: SyncValueChangePacket) + + fun handleSyncSetDelta(packet: SyncSetDeltaPacket) + + fun handleSetVelocitySecret(packet: ClientboundSetVelocitySecretPacket) + fun handlePacket(packet: NettyPacket) } \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/RunningProtocols.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/RunningProtocols.kt index db1496c4..62f6a713 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/RunningProtocols.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/RunningProtocols.kt @@ -31,7 +31,7 @@ object RunningProtocols { .addPacket(ClientboundSendResourcePacksPacket.STREAM_CODEC) .addPacket(ClientboundRemoveResourcePacksPacket.STREAM_CODEC) .addPacket(ClientboundClearResourcePacksPacket.STREAM_CODEC) - .addPacket(PlayerConnectToServerPacket::class.createCodec()) + .addPacket(PlayerConnectedToServerPacket::class.createCodec()) .addPacket(PlayerConnectToServerResponsePacket::class.createCodec()) .addPacket(PlayerDisconnectFromServerPacket.STREAM_CODEC) .addPacket(ClientboundRequestDisplayNamePacket.STREAM_CODEC) @@ -51,7 +51,6 @@ object RunningProtocols { .addPacket(ClientboundRegisterCloudServersToProxyPacket.STREAM_CODEC) .addPacket(ClientboundTriggerShutdownPacket.STREAM_CODEC) .addPacket(RequestOfflineDisplayNamePacket.STREAM_CODEC) - .addPacket(ClientboundBatchUpdateServer.STREAM_CODEC) .addPacket(ServerboundRequestPlayerDataResponse.STREAM_CODEC) .addPacket(PullPlayersToGroupResponsePacket::class.createCodec()) .addPacket(SilentDisconnectPlayerPacket::class.createCodec()) @@ -67,6 +66,9 @@ object RunningProtocols { .addPacket(ClientboundTriggerPunishmentCreatedEventPacket::class.createCodec()) .addPacket(ClientboundFetchIpAddressesResponsePacket::class.createCodec()) .addPacket(RequestPlayerPermissionPacket::class.createCodec()) + .addPacket(SyncValueChangePacket.STREAM_CODEC) + .addPacket(SyncSetDeltaPacket.STREAM_CODEC) + .addPacket(ClientboundSetVelocitySecretPacket::class.createCodec()) } val CLIENTBOUND by lazy { CLIENTBOUND_TEMPLATE.freeze().bind(::SurfByteBuf) } @@ -134,6 +136,8 @@ object RunningProtocols { .addPacket(ServerboundFetchIpBansPacket::class.createCodec()) .addPacket(RequestPlayerPermissionPacket::class.createCodec()) .addPacket(ServerboundQueuePlayerToGroupPacket::class.createCodec()) + .addPacket(SyncValueChangePacket.STREAM_CODEC) + .addPacket(SyncSetDeltaPacket.STREAM_CODEC) } val SERVERBOUND by lazy { SERVERBOUND_TEMPLATE.freeze().bind(::SurfByteBuf) } diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/RunningServerPacketListener.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/RunningServerPacketListener.kt index 8b29e7ed..06a82361 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/RunningServerPacketListener.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/RunningServerPacketListener.kt @@ -104,5 +104,9 @@ interface RunningServerPacketListener : ServerCommonPacketListener, TickablePack suspend fun handleRequestPlayerPermission(packet: RequestPlayerPermissionPacket) + fun handleSyncValueChange(packet: SyncValueChangePacket) + + fun handleSyncSetDelta(packet: SyncSetDeltaPacket) + fun handlePacket(packet: NettyPacket) } \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/ServerboundPlayerPersistentDataContainerUpdatePacket.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/ServerboundPlayerPersistentDataContainerUpdatePacket.kt index a2010cb6..a4c384c4 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/ServerboundPlayerPersistentDataContainerUpdatePacket.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/ServerboundPlayerPersistentDataContainerUpdatePacket.kt @@ -6,7 +6,7 @@ import dev.slne.surf.cloud.api.common.netty.network.protocol.PacketFlow import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket import dev.slne.surf.cloud.api.common.netty.packet.packetCodec import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf -import net.querz.nbt.tag.CompoundTag +import net.kyori.adventure.nbt.CompoundBinaryTag import java.util.* @SurfNettyPacket( @@ -23,9 +23,9 @@ class ServerboundPlayerPersistentDataContainerUpdatePacket : NettyPacket { val uuid: UUID val verificationId: Int - val nbt: CompoundTag + val nbt: CompoundBinaryTag - constructor(uuid: UUID, verificationId: Int, nbt: CompoundTag) { + constructor(uuid: UUID, verificationId: Int, nbt: CompoundBinaryTag) { this.uuid = uuid this.verificationId = verificationId this.nbt = nbt diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/SyncSetDeltaPacket.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/SyncSetDeltaPacket.kt new file mode 100644 index 00000000..02e6889c --- /dev/null +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/SyncSetDeltaPacket.kt @@ -0,0 +1,63 @@ +package dev.slne.surf.cloud.core.common.netty.network.protocol.running + +import dev.slne.surf.cloud.api.common.meta.SurfNettyPacket +import dev.slne.surf.cloud.api.common.netty.network.protocol.PacketFlow +import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket +import dev.slne.surf.cloud.api.common.netty.packet.packetCodec +import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf +import dev.slne.surf.cloud.core.common.sync.CommonSyncRegistryImpl +import dev.slne.surf.cloud.core.common.sync.SyncSetImpl + +@SurfNettyPacket( + "cloud:sync_set_delta", + PacketFlow.BIDIRECTIONAL, +) +class SyncSetDeltaPacket : NettyPacket { + companion object { + val STREAM_CODEC = packetCodec(SyncSetDeltaPacket::write, ::SyncSetDeltaPacket) + } + + val setId: String + val added: Boolean + val changeId: Long + val element: Any? + val registered: Boolean + + constructor( + syncSet: SyncSetImpl<*>, + added: Boolean, + changeId: Long, + element: Any? + ) { + this.setId = syncSet.id + this.added = added + this.changeId = changeId + this.element = element + this.registered = true + } + + constructor(buf: SurfByteBuf) { + this.setId = buf.readUtf() + this.added = buf.readBoolean() + this.changeId = buf.readLong() + val codec = CommonSyncRegistryImpl.instance.getSet(setId)?.valueCodec + + if (codec == null) { + this.registered = false + this.element = null + buf.skipBytes(buf.readableBytes()) + } else { + this.registered = true + this.element = codec.decode(buf) + } + } + + private fun write(buf: SurfByteBuf) { + buf.writeUtf(setId) + buf.writeBoolean(added) + buf.writeLong(changeId) + val codec = CommonSyncRegistryImpl.instance.getSet(setId) + ?: error("SyncSet '$setId' is not registered") + codec.valueCodec.encode(buf, element) + } +} \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/SyncValueChangePacket.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/SyncValueChangePacket.kt new file mode 100644 index 00000000..1981781a --- /dev/null +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/running/SyncValueChangePacket.kt @@ -0,0 +1,63 @@ +package dev.slne.surf.cloud.core.common.netty.network.protocol.running + +import dev.slne.surf.cloud.api.common.meta.SurfNettyPacket +import dev.slne.surf.cloud.api.common.netty.network.protocol.PacketFlow +import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket +import dev.slne.surf.cloud.api.common.netty.packet.packetCodec +import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf +import dev.slne.surf.cloud.core.common.sync.BasicSyncValue +import dev.slne.surf.cloud.core.common.sync.CommonSyncRegistryImpl + +@SurfNettyPacket("cloud:sync_value_change", PacketFlow.BIDIRECTIONAL) +class SyncValueChangePacket : NettyPacket { + + companion object { + val STREAM_CODEC = packetCodec(SyncValueChangePacket::write, ::SyncValueChangePacket) + } + + val origin: Long? + val syncId: String + val value: Any? + val registered: Boolean + + constructor(origin: Long?, value: BasicSyncValue<*>) { + this.origin = origin + this.syncId = value.id + this.value = value.get() + this.registered = true + } + + constructor(buf: SurfByteBuf) { + this.origin = buf.readNullableLong() + this.syncId = buf.readUtf() + val codec = CommonSyncRegistryImpl.instance.getSyncValueCodec(syncId) + + if (codec == null) { + this.value = null + this.registered = false + buf.skipBytes(buf.readableBytes()) + } else { + this.registered = true + this.value = codec.decode(buf) + } + } + + private fun write(buf: SurfByteBuf) { + buf.writeNullable(origin) + buf.writeUtf(syncId) + val codec = CommonSyncRegistryImpl.instance.getSyncValueCodec(syncId) + ?: error("'$syncId' is not registered in SyncRegistry") + + codec.encode(buf, value) + } + + override fun toString(): String { + return "SyncValueChangePacket(" + + "origin=$origin, " + + "syncId='$syncId', " + + "value=$value" + + ")" + + " ${super.toString()}" + } + +} \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/ClientSynchronizingPacketListener.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/ClientSynchronizingPacketListener.kt new file mode 100644 index 00000000..e9640263 --- /dev/null +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/ClientSynchronizingPacketListener.kt @@ -0,0 +1,29 @@ +package dev.slne.surf.cloud.core.common.netty.network.protocol.synchronizing + +import dev.slne.surf.cloud.api.common.netty.network.ConnectionProtocol +import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket +import dev.slne.surf.cloud.core.common.netty.network.protocol.common.ClientCommonPacketListener +import dev.slne.surf.cloud.core.common.netty.network.protocol.common.ClientboundSetVelocitySecretPacket +import dev.slne.surf.cloud.core.common.netty.network.protocol.running.ClientboundBatchUpdateServer +import dev.slne.surf.cloud.core.common.netty.network.protocol.running.SyncSetDeltaPacket +import dev.slne.surf.cloud.core.common.netty.network.protocol.running.SyncValueChangePacket + +interface ClientSynchronizingPacketListener : ClientCommonPacketListener { + override val protocol get() = ConnectionProtocol.SYNCHRONIZING + + suspend fun handleSynchronizeFinish(packet: ClientboundSynchronizeFinishPacket) + + fun handleSyncValueChange(packet: SyncValueChangePacket) + + fun handleBatchSyncValue(packet: ClientboundBatchSyncValuePacket) + + fun handleBatchSyncSet(packet: ClientboundBatchSyncSetPacket) + + suspend fun handleBatchUpdateServer(packet: ClientboundBatchUpdateServer) + + fun handleSyncSetDelta(packet: SyncSetDeltaPacket) + + fun handleSetVelocitySecret(packet: ClientboundSetVelocitySecretPacket) + + fun handlePacket(packet: NettyPacket) +} \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/ClientboundBatchSyncSetPacket.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/ClientboundBatchSyncSetPacket.kt new file mode 100644 index 00000000..fde12c6c --- /dev/null +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/ClientboundBatchSyncSetPacket.kt @@ -0,0 +1,73 @@ +package dev.slne.surf.cloud.core.common.netty.network.protocol.synchronizing + +import dev.slne.surf.cloud.api.common.meta.SurfNettyPacket +import dev.slne.surf.cloud.api.common.netty.network.ConnectionProtocol +import dev.slne.surf.cloud.api.common.netty.network.protocol.PacketFlow +import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket +import dev.slne.surf.cloud.api.common.netty.packet.packetCodec +import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf +import dev.slne.surf.cloud.api.common.util.objectListOf +import dev.slne.surf.cloud.core.common.sync.CommonSyncRegistryImpl +import dev.slne.surf.cloud.core.common.sync.SyncSetImpl +import dev.slne.surf.surfapi.core.api.util.logger + +@SurfNettyPacket( + "cloud:batch_sync_set", + PacketFlow.BIDIRECTIONAL, + ConnectionProtocol.SYNCHRONIZING +) +class ClientboundBatchSyncSetPacket : NettyPacket { + companion object { + private val log = logger() + val STREAM_CODEC = + packetCodec(ClientboundBatchSyncSetPacket::write, ::ClientboundBatchSyncSetPacket) + } + + val syncSets: List>> + + constructor(syncValues: Map>) { + this.syncSets = syncValues.map { (key, value) -> key to value.toSet() } + } + + private constructor(buf: SurfByteBuf) { + val unknownSyncValues = objectListOf() + + syncSets = buf.readList { buf -> + val syncId = buf.readUtf() + val syncSize = buf.readInt() + + val syncSet = CommonSyncRegistryImpl.Companion.instance.getSet(syncId) + if (syncSet == null) { + buf.skipBytes(syncSize) + unknownSyncValues.add(syncId) + null + } else { + syncId to syncSet.codec.decode(buf) + } + }.filterNotNull() + + if (unknownSyncValues.isNotEmpty()) { + log.atWarning() + .log("Unknown sync sets: [${unknownSyncValues.joinToString(", ")}]") + } + } + + private fun write(buf: SurfByteBuf) { + buf.writeCollection(syncSets) { buf, (syncId, set) -> + buf.writeUtf(syncId) + + // Reserve 4 bytes for length + val lengthIndex = buf.writerIndex() + buf.writeInt(0) + + val startIndex = buf.writerIndex() + val syncSet = CommonSyncRegistryImpl.Companion.instance.getSet(syncId) + ?: error("SyncSet '$syncId' is not registered in SyncRegistry") + syncSet.codec.encode(buf, set) + val endIndex = buf.writerIndex() + + // Write the actual length of the encoded value + buf.setInt(lengthIndex, endIndex - startIndex) + } + } +} \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/ClientboundBatchSyncValuePacket.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/ClientboundBatchSyncValuePacket.kt new file mode 100644 index 00000000..f0de96c4 --- /dev/null +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/ClientboundBatchSyncValuePacket.kt @@ -0,0 +1,73 @@ +package dev.slne.surf.cloud.core.common.netty.network.protocol.synchronizing + +import dev.slne.surf.cloud.api.common.meta.SurfNettyPacket +import dev.slne.surf.cloud.api.common.netty.network.ConnectionProtocol +import dev.slne.surf.cloud.api.common.netty.network.protocol.PacketFlow +import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket +import dev.slne.surf.cloud.api.common.netty.packet.packetCodec +import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf +import dev.slne.surf.cloud.core.common.sync.BasicSyncValue +import dev.slne.surf.cloud.core.common.sync.CommonSyncRegistryImpl +import dev.slne.surf.surfapi.core.api.util.logger +import dev.slne.surf.surfapi.core.api.util.mutableObjectListOf + +@SurfNettyPacket( + "cloud:batch_sync_value", + PacketFlow.BIDIRECTIONAL, + ConnectionProtocol.SYNCHRONIZING +) +class ClientboundBatchSyncValuePacket : NettyPacket { + companion object { + private val log = logger() + val STREAM_CODEC = + packetCodec(ClientboundBatchSyncValuePacket::write, ::ClientboundBatchSyncValuePacket) + } + + val syncValues: List> + + constructor(syncValues: Map>) { + this.syncValues = syncValues.map { (key, value) -> key to value.get() } + } + + private constructor(buf: SurfByteBuf) { + val unknownSyncValues = mutableObjectListOf() + + syncValues = buf.readList { buf -> + val syncId = buf.readUtf() + val syncSize = buf.readInt() + + val codec = CommonSyncRegistryImpl.Companion.instance.getSyncValueCodec(syncId) + if (codec == null) { + buf.skipBytes(syncSize) + unknownSyncValues.add(syncId) + null + } else { + syncId to codec.decode(buf) + } + }.filterNotNull() + + if (unknownSyncValues.isNotEmpty()) { + log.atWarning() + .log("Unknown sync values: [${unknownSyncValues.joinToString(", ")}]") + } + } + + private fun write(buf: SurfByteBuf) { + buf.writeCollection(syncValues) { buf, (syncId, value) -> + buf.writeUtf(syncId) + + // Reserve 4 bytes for length + val lengthIndex = buf.writerIndex() + buf.writeInt(0) + + val startIndex = buf.writerIndex() + val codec = CommonSyncRegistryImpl.Companion.instance.getSyncValueCodec(syncId) + ?: error("'$syncId' is not registered in SyncRegistry") + codec.encode(buf, value) + val endIndex = buf.writerIndex() + + // Write the actual length of the encoded value + buf.setInt(lengthIndex, endIndex - startIndex) + } + } +} \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/ClientboundSynchronizeFinishPacket.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/ClientboundSynchronizeFinishPacket.kt new file mode 100644 index 00000000..4897e7fd --- /dev/null +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/ClientboundSynchronizeFinishPacket.kt @@ -0,0 +1,17 @@ +package dev.slne.surf.cloud.core.common.netty.network.protocol.synchronizing + +import dev.slne.surf.cloud.api.common.meta.SurfNettyPacket +import dev.slne.surf.cloud.api.common.netty.network.ConnectionProtocol +import dev.slne.surf.cloud.api.common.netty.network.codec.streamCodecUnitSimple +import dev.slne.surf.cloud.api.common.netty.network.protocol.PacketFlow +import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket + +@SurfNettyPacket( + "cloud:clientbound:synchronize_finish", + PacketFlow.CLIENTBOUND, + ConnectionProtocol.SYNCHRONIZING +) +object ClientboundSynchronizeFinishPacket : NettyPacket() { + val STREAM_CODEC = streamCodecUnitSimple(ClientboundSynchronizeFinishPacket) + override val terminal: Boolean = true +} \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/FinishSynchronizingPacket.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/FinishSynchronizingPacket.kt new file mode 100644 index 00000000..a18bd624 --- /dev/null +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/FinishSynchronizingPacket.kt @@ -0,0 +1,15 @@ +package dev.slne.surf.cloud.core.common.netty.network.protocol.synchronizing + +import dev.slne.surf.cloud.api.common.netty.network.ConnectionProtocol +import dev.slne.surf.cloud.api.common.netty.network.codec.streamCodecUnitSimple +import dev.slne.surf.cloud.api.common.netty.network.protocol.PacketFlow +import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket + +@dev.slne.surf.cloud.api.common.meta.SurfNettyPacket( + "cloud:bidirectional:finish_synchronize", + PacketFlow.BIDIRECTIONAL, + ConnectionProtocol.SYNCHRONIZING +) +object FinishSynchronizingPacket: NettyPacket() { + val STREAM_CODEC = streamCodecUnitSimple(FinishSynchronizingPacket) +} \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/ServerSynchronizingPacketListener.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/ServerSynchronizingPacketListener.kt new file mode 100644 index 00000000..5aeace4d --- /dev/null +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/ServerSynchronizingPacketListener.kt @@ -0,0 +1,21 @@ +package dev.slne.surf.cloud.core.common.netty.network.protocol.synchronizing + +import dev.slne.surf.cloud.api.common.netty.network.ConnectionProtocol +import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket +import dev.slne.surf.cloud.core.common.netty.network.protocol.common.ServerCommonPacketListener +import dev.slne.surf.cloud.core.common.netty.network.protocol.running.SyncSetDeltaPacket +import dev.slne.surf.cloud.core.common.netty.network.protocol.running.SyncValueChangePacket + +interface ServerSynchronizingPacketListener : ServerCommonPacketListener { + override val protocol get() = ConnectionProtocol.SYNCHRONIZING + + suspend fun handleFinishSynchronizing(packet: FinishSynchronizingPacket) + + suspend fun handleSynchronizeFinishAcknowledged(packet: ServerboundSynchronizeFinishAcknowledgedPacket) + + fun handleSyncValueChange(packet: SyncValueChangePacket) + + fun handleSyncSetDelta(packet: SyncSetDeltaPacket) + + fun handlePacket(packet: NettyPacket) +} \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/ServerboundSynchronizeFinishAcknowledgedPacket.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/ServerboundSynchronizeFinishAcknowledgedPacket.kt new file mode 100644 index 00000000..167a3b0b --- /dev/null +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/ServerboundSynchronizeFinishAcknowledgedPacket.kt @@ -0,0 +1,17 @@ +package dev.slne.surf.cloud.core.common.netty.network.protocol.synchronizing + +import dev.slne.surf.cloud.api.common.meta.SurfNettyPacket +import dev.slne.surf.cloud.api.common.netty.network.ConnectionProtocol +import dev.slne.surf.cloud.api.common.netty.network.codec.streamCodecUnitSimple +import dev.slne.surf.cloud.api.common.netty.network.protocol.PacketFlow +import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket + +@SurfNettyPacket( + "cloud:serverbound:synchronize_finish_acknowledged", + PacketFlow.SERVERBOUND, + ConnectionProtocol.SYNCHRONIZING +) +object ServerboundSynchronizeFinishAcknowledgedPacket : NettyPacket() { + val STREAM_CODEC = streamCodecUnitSimple(ServerboundSynchronizeFinishAcknowledgedPacket) + override val terminal = true +} \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/StartSynchronizingPacket.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/StartSynchronizingPacket.kt new file mode 100644 index 00000000..d9d7fa28 --- /dev/null +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/StartSynchronizingPacket.kt @@ -0,0 +1,16 @@ +package dev.slne.surf.cloud.core.common.netty.network.protocol.synchronizing + +import dev.slne.surf.cloud.api.common.meta.SurfNettyPacket +import dev.slne.surf.cloud.api.common.netty.network.ConnectionProtocol +import dev.slne.surf.cloud.api.common.netty.network.codec.streamCodecUnitSimple +import dev.slne.surf.cloud.api.common.netty.network.protocol.PacketFlow +import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket + +@SurfNettyPacket( + "cloud:bidirectional:start_synchronize", + PacketFlow.BIDIRECTIONAL, + ConnectionProtocol.SYNCHRONIZING +) +object StartSynchronizingPacket : NettyPacket() { + val STREAM_CODEC = streamCodecUnitSimple(StartSynchronizingPacket) +} \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/SynchronizingProtocols.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/SynchronizingProtocols.kt new file mode 100644 index 00000000..021b6dda --- /dev/null +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/protocol/synchronizing/SynchronizingProtocols.kt @@ -0,0 +1,47 @@ +package dev.slne.surf.cloud.core.common.netty.network.protocol.synchronizing + +import dev.slne.surf.cloud.api.common.netty.network.ConnectionProtocol +import dev.slne.surf.cloud.api.common.netty.packet.createCodec +import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf +import dev.slne.surf.cloud.core.common.netty.network.protocol.ProtocolInfoBuilder +import dev.slne.surf.cloud.core.common.netty.network.protocol.common.* +import dev.slne.surf.cloud.core.common.netty.network.protocol.running.ClientboundBatchUpdateServer +import dev.slne.surf.cloud.core.common.netty.network.protocol.running.SyncSetDeltaPacket +import dev.slne.surf.cloud.core.common.netty.network.protocol.running.SyncValueChangePacket + +object SynchronizingProtocols { + + val CLIENTBOUND_TEMPLATE = + ProtocolInfoBuilder.mutableClientboundProtocol( + ConnectionProtocol.SYNCHRONIZING + ) { builder -> + builder.withBundlePacket(::ClientboundBundlePacket, ClientboundBundleDelimiterPacket()) + builder.addPacket(KeepAlivePacket::class.createCodec()) + .addPacket(ClientboundPongResponsePacket.STREAM_CODEC) + .addPacket(ClientboundBatchUpdateServer.STREAM_CODEC) + .addPacket(SyncValueChangePacket.STREAM_CODEC) + .addPacket(ClientboundBatchSyncValuePacket.STREAM_CODEC) + .addPacket(ClientboundBatchSyncSetPacket.STREAM_CODEC) + .addPacket(SyncSetDeltaPacket.STREAM_CODEC) + .addPacket(FinishSynchronizingPacket.STREAM_CODEC) + .addPacket(ClientboundSynchronizeFinishPacket.STREAM_CODEC) + .addPacket(ClientboundSetVelocitySecretPacket::class.createCodec()) + } + + val CLIENTBOUND by lazy { CLIENTBOUND_TEMPLATE.bind(::SurfByteBuf) } + + val SERVERBOUND_TEMPLATE = + ProtocolInfoBuilder.mutableServerboundProtocol( + ConnectionProtocol.SYNCHRONIZING + ) { builder -> + builder.withBundlePacket(::ServerboundBundlePacket, ServerboundBundleDelimiterPacket()) + builder.addPacket(KeepAlivePacket::class.createCodec()) + .addPacket(ServerboundPingRequestPacket.STREAM_CODEC) + .addPacket(ServerboundSynchronizeFinishAcknowledgedPacket.STREAM_CODEC) + .addPacket(SyncValueChangePacket.STREAM_CODEC) + .addPacket(SyncSetDeltaPacket.STREAM_CODEC) + .addPacket(FinishSynchronizingPacket.STREAM_CODEC) + } + + val SERVERBOUND by lazy { SERVERBOUND_TEMPLATE.bind(::SurfByteBuf) } +} \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/CloudPlayerManagerImpl.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/CloudPlayerManagerImpl.kt index 300feaa3..2384ea7f 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/CloudPlayerManagerImpl.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/CloudPlayerManagerImpl.kt @@ -4,17 +4,18 @@ import dev.slne.surf.cloud.api.common.event.player.connection.CloudPlayerConnect import dev.slne.surf.cloud.api.common.event.player.connection.CloudPlayerDisconnectFromNetworkEvent import dev.slne.surf.cloud.api.common.player.CloudPlayer import dev.slne.surf.cloud.api.common.player.CloudPlayerManager +import dev.slne.surf.cloud.api.common.player.task.PrePlayerJoinTask import dev.slne.surf.cloud.api.common.server.UserList import dev.slne.surf.cloud.api.common.server.UserListImpl import dev.slne.surf.cloud.api.common.util.TimeLogger import dev.slne.surf.cloud.api.common.util.mutableObject2ObjectMapOf import dev.slne.surf.cloud.api.common.util.synchronize -import dev.slne.surf.cloud.core.common.player.task.PrePlayerJoinTask -import dev.slne.surf.cloud.core.common.player.task.PrePlayerJoinTaskManager import dev.slne.surf.cloud.core.common.spring.CloudLifecycleAware -import dev.slne.surf.cloud.core.common.util.publish import dev.slne.surf.surfapi.core.api.util.logger import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import org.jetbrains.annotations.MustBeInvokedByOverriders import org.springframework.core.annotation.Order import org.springframework.stereotype.Component @@ -24,6 +25,9 @@ import java.util.* abstract class CloudPlayerManagerImpl

: CloudPlayerManager { private val log = logger() protected val players = mutableObject2ObjectMapOf().synchronize() + protected val creatingPlayers = + mutableObject2ObjectMapOf>().synchronize() + protected val createMutex = Mutex() override fun getPlayer(uuid: UUID?): P? { return players[uuid] @@ -76,19 +80,23 @@ abstract class CloudPlayerManagerImpl

: CloudPlayerMa name: String, proxy: Boolean, ip: Inet4Address, - serverUid: Long + serverUid: Long, + runPreJoinTasks: Boolean ): PrePlayerJoinTask.Result { - val player = players[uuid] + val (player, preJoinResult, created) = getOrCreatePlayerAtomically( + uuid, + name, + proxy, + ip, + serverUid, + runPreJoinTasks + ) if (player == null) { - createPlayer(uuid, name, proxy, ip, serverUid).also { - val preJoinResult = preJoin(it) - if (preJoinResult !is PrePlayerJoinTask.Result.ALLOWED) return preJoinResult - onNetworkConnect(uuid, it) - onServerConnect(uuid, it, serverUid) - addPlayer(it) - } - } else { + return preJoinResult + } + + if (!created) { if (proxy) { updateProxyServer(player, serverUid) } else { @@ -98,6 +106,105 @@ abstract class CloudPlayerManagerImpl

: CloudPlayerMa } return PrePlayerJoinTask.Result.ALLOWED + + +// val player = players[uuid] +// val creatingPlayer = creatingPlayers[uuid] +// +// if (player == null && creatingPlayer == null) { +// val creatingPlayerDeferred = CompletableDeferred() +// creatingPlayers[uuid] = creatingPlayerDeferred +// +// createPlayer(uuid, name, proxy, ip, serverUid).also { +// if (runPreJoinTasks) { +// val preJoinResult = preJoin(it) +// if (preJoinResult !is PrePlayerJoinTask.Result.ALLOWED) { +// creatingPlayerDeferred.complete(null) +// return preJoinResult +// } +// } +// +// onNetworkConnect(uuid, it) +// onServerConnect(uuid, it, serverUid) +// addPlayer(it) +// } +// } else { +// coroutineScope { +// if (proxy) { +// if (player != null) { +// updateProxyServer(player, serverUid) +// } else { +// launch { +// creatingPlayer?.await()?.let { newPlayer -> +// updateProxyServer(newPlayer, serverUid) +// } +// } +// } +// } else { +// if (player != null) { +// updateServer(player, serverUid) +// } else { +// launch { +// creatingPlayer?.await()?.let { newPlayer -> +// updateServer(newPlayer, serverUid) +// } +// } +// } +// } +// +// if (player != null) { +// onServerConnect(uuid, player, serverUid) +// } else { +// launch { +// creatingPlayer?.await()?.let { newPlayer -> +// onServerConnect(uuid, newPlayer, serverUid) +// } +// } +// } +// } +// } +// +// return PrePlayerJoinTask.Result.ALLOWED + } + + private suspend fun getOrCreatePlayerAtomically( + uuid: UUID, + name: String, + proxy: Boolean, + ip: Inet4Address, + serverUid: Long, + runPreJoinTasks: Boolean + ): Triple { + players[uuid]?.let { return Triple(it, PrePlayerJoinTask.Result.ALLOWED, false) } + + var needsCreation = false + val creatingPlayer = createMutex.withLock { + creatingPlayers.computeIfAbsent(uuid) { + needsCreation = true + CompletableDeferred() + } + } + + if (!creatingPlayer.isCompleted && needsCreation) { + val newPlayer = createPlayer(uuid, name, proxy, ip, serverUid) + + if (runPreJoinTasks) { + val preJoinResult = preJoin(newPlayer) + if (preJoinResult !is PrePlayerJoinTask.Result.ALLOWED) { + creatingPlayer.complete(null) + return Triple(null, preJoinResult, true) + } + } + + addPlayer(newPlayer) + creatingPlayers.remove(uuid) + + onNetworkConnect(uuid, newPlayer) + onServerConnect(uuid, newPlayer, serverUid) + creatingPlayer.complete(newPlayer) + } + + return Triple(creatingPlayer.await(), PrePlayerJoinTask.Result.ALLOWED, true) } protected open suspend fun preJoin(player: P): PrePlayerJoinTask.Result { diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/CommonCloudPlayerImpl.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/CommonCloudPlayerImpl.kt index 6819e0a6..9a4f38e8 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/CommonCloudPlayerImpl.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/CommonCloudPlayerImpl.kt @@ -52,6 +52,10 @@ abstract class CommonCloudPlayerImpl(uuid: UUID, override val name: String) : return true } + override suspend fun lastServer(): CloudServer { + return currentServer() + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is CommonCloudPlayerImpl) return false diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/ppdc/PersistentPlayerDataContainerImpl.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/ppdc/PersistentPlayerDataContainerImpl.kt index 3cf8c631..ef6ea0a8 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/ppdc/PersistentPlayerDataContainerImpl.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/ppdc/PersistentPlayerDataContainerImpl.kt @@ -3,17 +3,21 @@ package dev.slne.surf.cloud.core.common.player.ppdc import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf import dev.slne.surf.cloud.api.common.player.ppdc.PersistentPlayerDataContainer import dev.slne.surf.cloud.api.common.player.ppdc.PersistentPlayerDataType -import dev.slne.surf.cloud.api.common.util.nbt.set +import dev.slne.surf.surfapi.core.api.nbt.FastCompoundBinaryTag +import dev.slne.surf.surfapi.core.api.nbt.fast import net.kyori.adventure.key.Key -import net.querz.nbt.io.NBTInputStream -import net.querz.nbt.tag.CompoundTag -import net.querz.nbt.tag.Tag -import java.io.ByteArrayInputStream +import net.kyori.adventure.nbt.* class PersistentPlayerDataContainerImpl( - private val tag: CompoundTag = CompoundTag() + + @Volatile + private var tag: FastCompoundBinaryTag = CompoundBinaryTag.empty().fast(synchronize = true) ) : PersistentPlayerDataContainerViewImpl(), PersistentPlayerDataContainer { - override fun getTag(key: String): Tag<*>? = tag[key] + override fun getTag(key: String) = tag.get(key) + + private inline fun getTag(key: Key): T? { + return tag.get(key.asString()) as? T + } override fun

set( key: Key, @@ -29,103 +33,101 @@ class PersistentPlayerDataContainerImpl( ) } - fun put(key: String, value: Tag<*>) { - tag[key] = value + fun put(key: String, value: BinaryTag) { + tag.put(key, value) } override fun setBoolean(key: Key, value: Boolean) { - tag[key.asString()] = value + tag.putBoolean(key.asString(), value) } override fun setByte(key: Key, value: Byte) { - tag[key.asString()] = value + tag.putByte(key.asString(), value) } override fun setShort(key: Key, value: Short) { - tag[key.asString()] = value + tag.putShort(key.asString(), value) } override fun setInt(key: Key, value: Int) { - tag[key.asString()] = value + tag.putInt(key.asString(), value) } override fun setLong(key: Key, value: Long) { - tag[key.asString()] = value + tag.putLong(key.asString(), value) } override fun setFloat(key: Key, value: Float) { - tag[key.asString()] = value + tag.putFloat(key.asString(), value) } override fun setDouble(key: Key, value: Double) { - tag[key.asString()] = value + tag.putDouble(key.asString(), value) } override fun setString(key: Key, value: String) { - tag[key.asString()] = value + tag.putString(key.asString(), value) } override fun setByteArray(key: Key, value: ByteArray) { - tag[key.asString()] = value + tag.putByteArray(key.asString(), value) } override fun setIntArray(key: Key, value: IntArray) { - tag[key.asString()] = value + tag.putIntArray(key.asString(), value) } override fun setLongArray(key: Key, value: LongArray) { - tag[key.asString()] = value + tag.putLongArray(key.asString(), value) } override fun getBoolean(key: Key): Boolean? { - return tag.getByteTag(key.asString())?.let { it.asByte() > 0 } + val tag = getTag(key) ?: return null + return tag.value() != 0.toByte() } override fun getNumber(key: Key): Number? { - val stringKey = key.asString() - if (!tag.containsKey(stringKey)) return null - - return tag.getNumber(stringKey) + return getTag(key)?.numberValue() } override fun getByte(key: Key): Byte? { - return tag.getByteTag(key.asString())?.asByte() + return getTag(key)?.value() } override fun getShort(key: Key): Short? { - return tag.getShortTag(key.asString())?.asShort() + return getTag(key)?.value() } override fun getInt(key: Key): Int? { - return tag.getIntTag(key.asString())?.asInt() + return getTag(key)?.value() } override fun getLong(key: Key): Long? { - return tag.getLongTag(key.asString())?.asLong() + return getTag(key)?.value() } override fun getFloat(key: Key): Float? { - return tag.getFloatTag(key.asString())?.asFloat() + return getTag(key)?.value() } override fun getDouble(key: Key): Double? { - return tag.getDoubleTag(key.asString())?.asDouble() + return getTag(key)?.value() } override fun getString(key: Key): String? { - return tag.getString(key.asString()) + return getTag(key)?.value() } override fun getByteArray(key: Key): ByteArray? { - return tag.getByteArrayTag(key.asString())?.value + return getTag(key)?.value() } override fun getIntArray(key: Key): IntArray? { - return tag.getIntArrayTag(key.asString())?.value + return getTag(key)?.value() } override fun getLongArray(key: Key): LongArray? { - return tag.getLongArrayTag(key.asString())?.value + return getTag(key)?.value() } override fun remove(key: Key) { @@ -135,25 +137,14 @@ class PersistentPlayerDataContainerImpl( override val empty: Boolean get() = tag.size() == 0 - override fun toTagCompound(): CompoundTag = tag + override fun toTagCompound(): CompoundBinaryTag = tag.fast() override fun readFromBuf(buf: SurfByteBuf) { - tag.clear() - val size = buf.readVarInt() - val bytes = buf.readBytes(size).array() - - ByteArrayInputStream(bytes).use { - NBTInputStream(it).use { - val tag = it.readRawTag(Int.MAX_VALUE) - check(tag is CompoundTag) { "Expected a CompoundTag, got $tag" } - tag.forEach { (key, value) -> tag[key] = value } - } - } - } - - fun fromTagCompound(tag: CompoundTag) { - this.tag.clear() - tag.forEach { (key, value) -> this.tag[key] = value } + tag = buf.readCompoundTag().fast(synchronize = true) + } + + fun fromTagCompound(tag: CompoundBinaryTag) { + this.tag = tag.fast(synchronize = true) } override fun equals(other: Any?): Boolean { diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/ppdc/PersistentPlayerDataContainerViewImpl.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/ppdc/PersistentPlayerDataContainerViewImpl.kt index dfe0329a..ebecaccb 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/ppdc/PersistentPlayerDataContainerViewImpl.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/ppdc/PersistentPlayerDataContainerViewImpl.kt @@ -7,17 +7,14 @@ import dev.slne.surf.cloud.api.common.player.ppdc.PersistentPlayerDataType import dev.slne.surf.cloud.api.common.util.toObjectSet import it.unimi.dsi.fastutil.objects.ObjectSet import net.kyori.adventure.key.Key -import net.querz.nbt.io.NBTOutputStream -import net.querz.nbt.tag.CompoundTag -import net.querz.nbt.tag.Tag -import okio.use +import net.kyori.adventure.nbt.BinaryTag +import net.kyori.adventure.nbt.CompoundBinaryTag import org.jetbrains.annotations.Unmodifiable -import java.io.ByteArrayOutputStream abstract class PersistentPlayerDataContainerViewImpl : PersistentPlayerDataContainerView { - abstract fun toTagCompound(): CompoundTag - abstract fun getTag(key: String): Tag<*>? + abstract fun toTagCompound(): CompoundBinaryTag + abstract fun getTag(key: String): BinaryTag? override fun

has( key: Key, @@ -42,7 +39,7 @@ abstract class PersistentPlayerDataContainerViewImpl : PersistentPlayerDataConta } return type.fromPrimitive( - PersistentPlayerDataTypeRegistry.extract>(type, value), + PersistentPlayerDataTypeRegistry.extract(type, value), PersistentPlayerDataAdapterContextImpl ) } @@ -63,11 +60,6 @@ abstract class PersistentPlayerDataContainerViewImpl : PersistentPlayerDataConta override fun writeToBuf(buf: SurfByteBuf) { val root = toTagCompound() - ByteArrayOutputStream().use { - NBTOutputStream(it).use { it.writeRawTag(root, Int.MAX_VALUE) } - val bytes = it.toByteArray() - buf.writeVarInt(bytes.size) - buf.writeBytes(bytes) - } + buf.writeCompoundTag(root) } } \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/ppdc/PersistentPlayerDataTypeRegistry.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/ppdc/PersistentPlayerDataTypeRegistry.kt index 79d67c80..04b32122 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/ppdc/PersistentPlayerDataTypeRegistry.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/ppdc/PersistentPlayerDataTypeRegistry.kt @@ -6,9 +6,8 @@ import dev.slne.surf.cloud.api.common.player.ppdc.PersistentPlayerDataContainer import dev.slne.surf.cloud.api.common.player.ppdc.PersistentPlayerDataType import dev.slne.surf.cloud.api.common.util.mutableObject2ObjectMapOf import dev.slne.surf.cloud.api.common.util.mutableObjectListOf -import dev.slne.surf.cloud.api.common.util.nbt.getCompound import dev.slne.surf.cloud.api.common.util.synchronize -import net.querz.nbt.tag.* +import net.kyori.adventure.nbt.* import kotlin.reflect.KClass import kotlin.reflect.cast @@ -27,208 +26,234 @@ object PersistentPlayerDataTypeRegistry { } // region Primitives - if (Byte::class == type) { - return createAdapter( - Byte::class, - ByteTag::class, - ByteTag.ID, - ::ByteTag - ) { it.asByte() } - } else if (Short::class == type) { - return createAdapter( - Short::class, - ShortTag::class, - ShortTag.ID, - ::ShortTag - ) { it.asShort() } - } else if (Int::class == type) { - return createAdapter( - Int::class, - IntTag::class, - IntTag.ID, - ::IntTag - ) { it.asInt() } - } else if (Long::class == type) { - return createAdapter( - Long::class, - LongTag::class, - LongTag.ID, - ::LongTag - ) { it.asLong() } - } else if (Float::class == type) { - return createAdapter( - Float::class, - FloatTag::class, - FloatTag.ID, - ::FloatTag - ) { it.asFloat() } - } else if (Double::class == type) { - return createAdapter( - Double::class, - DoubleTag::class, - DoubleTag.ID, - ::DoubleTag - ) { it.asDouble() } - } else if (Boolean::class == type) { - return createAdapter( - Boolean::class, - ByteTag::class, - ByteTag.ID, - { if (it) ByteTag(1) else ByteTag(0) } - ) { it.asByte() == 1.toByte() } - } else if (Char::class == type) { - return createAdapter( - Char::class, - IntTag::class, - IntTag.ID, - { IntTag(it.code) } - ) { it.asInt().toChar() } - } else if (String::class == type) { - return createAdapter( - String::class, - StringTag::class, - StringTag.ID, - ::StringTag - ) { it.value } - } - // endregion - // region Primitive non-list arrays - else if (ByteArray::class == type) { - return createAdapter( - ByteArray::class, - ByteArrayTag::class, - ByteArrayTag.ID, - { ByteArrayTag(it.copyOf()) }, - { it.value.copyOf() } - ) - } else if (IntArray::class == type) { - return createAdapter( - IntArray::class, - IntArrayTag::class, - IntArrayTag.ID, - { IntArrayTag(it.copyOf()) }, - { it.value.copyOf() } - ) - } else if (LongArray::class == type) { - return createAdapter( - LongArray::class, - LongArrayTag::class, - LongArrayTag.ID, - { LongArrayTag(it.copyOf()) }, - { it.value.copyOf() } - ) - } else if (BooleanArray::class == type) { - return createAdapter( - BooleanArray::class, - ByteArrayTag::class, - ByteArrayTag.ID, - { ByteArrayTag(it.map { if (it) 1.toByte() else 0.toByte() }.toByteArray()) }, - { it.value.map { it == 1.toByte() }.toBooleanArray() } - ) - } else if (CharArray::class == type) { - return createAdapter( - CharArray::class, - IntArrayTag::class, - IntArrayTag.ID, - { IntArrayTag(it.map { it.code }.toIntArray()) }, - { it.value.map { it.toChar() }.toCharArray() } - ) - } - // endregion - - else if (Array::class == type) { - return createAdapter( - Array::class, - ListTag::class, - ListTag.ID, - { - ListTag(Tag::class.java).apply { - it.forEach { add((it as PersistentPlayerDataContainerImpl).toTagCompound()) } + when (type) { + Byte::class -> { + return createAdapter( + Byte::class, + BinaryTagTypes.BYTE, + ByteBinaryTag::byteBinaryTag + ) { it.value() } + } + + Short::class -> { + return createAdapter( + Short::class, + BinaryTagTypes.SHORT, + ShortBinaryTag::shortBinaryTag + ) { it.value() } + } + + Int::class -> { + return createAdapter( + Int::class, + BinaryTagTypes.INT, + IntBinaryTag::intBinaryTag + ) { it.value() } + } + + Long::class -> { + return createAdapter( + Long::class, + BinaryTagTypes.LONG, + LongBinaryTag::longBinaryTag + ) { it.value() } + } + + Float::class -> { + return createAdapter( + Float::class, + BinaryTagTypes.FLOAT, + FloatBinaryTag::floatBinaryTag + ) { it.value() } + } + + Double::class -> { + return createAdapter( + Double::class, + BinaryTagTypes.DOUBLE, + DoubleBinaryTag::doubleBinaryTag + ) { it.value() } + } + + Boolean::class -> { + return createAdapter( + Boolean::class, + BinaryTagTypes.BYTE, + { if (it) ByteBinaryTag.ONE else ByteBinaryTag.ZERO } + ) { it.value() != 0.toByte() } + } + + Char::class -> { + return createAdapter( + Char::class, + BinaryTagTypes.INT, + { IntBinaryTag.intBinaryTag(it.code) } + ) { it.value().toChar() } + } + + String::class -> { + return createAdapter( + String::class, + BinaryTagTypes.STRING, + StringBinaryTag::stringBinaryTag + ) { it.value() } + } + // endregion + // region Primitive non-list arrays + ByteArray::class -> { + return createAdapter( + ByteArray::class, + BinaryTagTypes.BYTE_ARRAY, + { ByteArrayBinaryTag.byteArrayBinaryTag(*it.copyOf()) }, + { it.value().copyOf() } + ) + } + + IntArray::class -> { + return createAdapter( + IntArray::class, + BinaryTagTypes.INT_ARRAY, + { IntArrayBinaryTag.intArrayBinaryTag(*it.copyOf()) }, + { it.value().copyOf() } + ) + } + + LongArray::class -> { + return createAdapter( + LongArray::class, + BinaryTagTypes.LONG_ARRAY, + { LongArrayBinaryTag.longArrayBinaryTag(*it.copyOf()) }, + { it.value().copyOf() } + ) + } + + BooleanArray::class -> { + return createAdapter( + BooleanArray::class, + BinaryTagTypes.BYTE_ARRAY, + { bytes -> + ByteArrayBinaryTag.byteArrayBinaryTag(*bytes.map { if (it) 1.toByte() else 0.toByte() } + .toByteArray()) + }, + { tag -> tag.value().map { it == 1.toByte() }.toBooleanArray() } + ) + } + + CharArray::class -> { + return createAdapter( + CharArray::class, + BinaryTagTypes.INT_ARRAY, + { ints -> + IntArrayBinaryTag.intArrayBinaryTag(*ints.map { it.code }.toIntArray()) + }, + { tag -> tag.value().map { it.toChar() }.toCharArray() } + ) + } + // endregion + Array::class -> { + return createAdapter( + Array::class, + BinaryTagTypes.LIST, + { pdcs -> + val builder = ListBinaryTag.builder(BinaryTagTypes.COMPOUND) + for (pdc in pdcs) { + require(pdc is PersistentPlayerDataContainerImpl) { "The PDC must be an instance of PersistentPlayerDataContainerImpl" } + builder.add(pdc.toTagCompound()) + } + builder.build() + }, { + it.mapIndexed { index, _ -> + val container = PersistentPlayerDataContainerImpl() + val compound = it.getCompound(index) + compound.forEach { (key, value) -> container.put(key, value) } + container + }.toTypedArray() } - }, { - it.mapIndexed { index, _ -> - val container = PersistentPlayerDataContainerImpl() - val compound = it.getCompound(index) - compound.forEach { key, value -> container.put(key, value) } - - container - }.toTypedArray() - } - ) - } else if (PersistentPlayerDataContainer::class == type) { - return createAdapter( - PersistentPlayerDataContainerImpl::class, - CompoundTag::class, - CompoundTag.ID, - { it.toTagCompound() }, - { - PersistentPlayerDataContainerImpl().apply { - it.forEach { key, value -> - put(key, value) + ) + } + + PersistentPlayerDataContainer::class -> { + return createAdapter( + PersistentPlayerDataContainerImpl::class, + BinaryTagTypes.COMPOUND, + { it.toTagCompound() }, + { + PersistentPlayerDataContainerImpl().apply { + it.forEach { (key, value) -> + put(key, value) + } } } - } - ) - } else if (List::class == type) { - @Suppress("UNCHECKED_CAST") - return createAdapter( - List::class, - ListTag::class, - ListTag.ID, - { type, value -> constructList(type as PersistentPlayerDataType, *>, value as List) }, - this::extractList, - this::matchesListTag - ) + ) + } + + List::class -> { + @Suppress("UNCHECKED_CAST") + return createAdapter( + List::class, + BinaryTagTypes.LIST, + { type, value -> + constructList( + type as PersistentPlayerDataType, *>, + value as List + ) + }, + this::extractList, + this::matchesListTag + ) + } } error("Could not find a valid TagAdapter implementation for the requested type ${type.simpleName}") } - private fun > createAdapter( + private fun createAdapter( primitiveType: KClass, - nbtBaseType: KClass, - serializedTypeByte: Byte, + nbtBaseType: BinaryTagType, builder: (T) -> Z, extractor: (Z) -> T ): TagAdapter { return createAdapter( primitiveType, nbtBaseType, - serializedTypeByte, { _, value -> builder(value) }, { _, value -> extractor(value) }, - { _, tag -> nbtBaseType.isInstance(tag) } + { _, tag -> nbtBaseType.test(tag.type()) } ) } - private fun > createAdapter( + private fun createAdapter( primitiveType: KClass, - nbtBaseType: KClass, - serializedTypeByte: Byte, + nbtBaseType: BinaryTagType, builder: (PersistentPlayerDataType, T) -> Z, extractor: (PersistentPlayerDataType, Z) -> T, - matcher: (PersistentPlayerDataType, Tag<*>) -> Boolean + matcher: (PersistentPlayerDataType, BinaryTag) -> Boolean ): TagAdapter { return TagAdapter(primitiveType, nbtBaseType, builder, extractor, matcher) } - fun

isInstanceOf(type: PersistentPlayerDataType, tag: Tag<*>): Boolean { - return getOrCreateAdapter>(type).isInstance(type, tag) + fun

isInstanceOf(type: PersistentPlayerDataType, tag: BinaryTag): Boolean { + return getOrCreateAdapter(type).isInstance(type, tag) } @Suppress("UNCHECKED_CAST") - private fun > getOrCreateAdapter(type: PersistentPlayerDataType): TagAdapter { + private fun getOrCreateAdapter(type: PersistentPlayerDataType): TagAdapter { return adapters.computeIfAbsent(type.primitiveType, createAdapter) as TagAdapter } - fun wrap(type: PersistentPlayerDataType, value: T): Tag<*> { - return getOrCreateAdapter>(type).build(type, value) + fun wrap(type: PersistentPlayerDataType, value: T): BinaryTag { + return getOrCreateAdapter(type).build(type, value) } - fun> extract(type: PersistentPlayerDataType, tag: Tag<*>): T { + fun extract(type: PersistentPlayerDataType, tag: BinaryTag): T { val primitiveType = type.primitiveType val adapter = getOrCreateAdapter(type) - require(adapter.isInstance(type, tag)) { "The found tag instance (${tag::class.simpleName}) cannot store ${primitiveType.simpleName}" } + require( + adapter.isInstance( + type, + tag + ) + ) { "The found tag instance (${tag::class.simpleName}) cannot store ${primitiveType.simpleName}" } val foundValue = adapter.extract(type, tag) require(primitiveType.isInstance(foundValue)) { "The found object is of the type ${foundValue::class.simpleName}. Expected type ${primitiveType.simpleName}" } @@ -239,57 +264,66 @@ object PersistentPlayerDataTypeRegistry { private fun

> constructList( type: PersistentPlayerDataType, list: List

- ): ListTag<*> { + ): ListBinaryTag { check(type is ListPersistentPlayerDataType<*, *>) { "The passed list cannot be written to the PDC with a ${type::class.simpleName} (expected a list data type)" } val type = type as ListPersistentPlayerDataType val elementType = type.elementType - getOrCreateAdapter>(elementType) + getOrCreateAdapter(elementType) val values = list.map { wrap(elementType, it) } - return ListTag(Tag::class.java).apply { addAll(values) } + return ListBinaryTag.heterogeneousListBinaryTag() + .add(values) + .build() } @Suppress("UNCHECKED_CAST") - private fun

extractList(type: PersistentPlayerDataType, listTag: ListTag<*>): List

{ + private fun

extractList( + type: PersistentPlayerDataType, + listTag: ListBinaryTag + ): List

{ check(type is ListPersistentPlayerDataType<*, *>) { "The found list tag cannot be read with a ${type::class.simpleName} (expected a list data type)" } val type = type as ListPersistentPlayerDataType val elementType = type.elementType val output = mutableObjectListOf

(listTag.size()) for (tag in listTag) { - output.add(extract>(elementType, tag)) + output.add(extract(elementType, tag)) } return output } - private fun matchesListTag(type: PersistentPlayerDataType, *>, tag: Tag<*>): Boolean { + private fun matchesListTag( + type: PersistentPlayerDataType, *>, + tag: BinaryTag + ): Boolean { if (type !is ListPersistentPlayerDataType<*, *>) { return false } - if (tag !is ListTag<*>) { + if (tag !is ListBinaryTag) { return false } - val elementType = tag.typeClass - val elementAdapter: TagAdapter> = getOrCreateAdapter(type.elementType) + val elementType = tag.elementType() + val elementAdapter: TagAdapter = getOrCreateAdapter(type.elementType) - return elementAdapter.nbtBaseType == elementType.kotlin || elementType == EndTag::class.java + return elementAdapter.nbtBaseType.test(elementType) || elementType == BinaryTagTypes.END } - internal data class TagAdapter

>( + internal data class TagAdapter

( val primitiveType: KClass

, - val nbtBaseType: KClass, + val nbtBaseType: BinaryTagType, val builder: (PersistentPlayerDataType, P) -> T, val extractor: (PersistentPlayerDataType, T) -> P, - val matcher: (PersistentPlayerDataType, Tag<*>) -> Boolean + val matcher: (PersistentPlayerDataType, BinaryTag) -> Boolean ) { - fun extract(dataType: PersistentPlayerDataType, base: Tag<*>): P { - require(nbtBaseType.isInstance(base)) { "The provided NBTBase was of the type ${base::class.simpleName}. Expected type ${nbtBaseType.simpleName}" } - return extractor(dataType, nbtBaseType.cast(base)) + @Suppress("UNCHECKED_CAST") + fun extract(dataType: PersistentPlayerDataType, base: BinaryTag): P { + require(nbtBaseType.test(base.type())) { "The provided NBTBase was of the type ${base.type()}. Expected type $nbtBaseType" } + return extractor(dataType, base as T) } fun build(dataType: PersistentPlayerDataType, value: Any): T { @@ -299,7 +333,7 @@ object PersistentPlayerDataTypeRegistry { fun isInstance( persistentDataType: PersistentPlayerDataType, - base: Tag<*> + base: BinaryTag ): Boolean = matcher(persistentDataType, base) } diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/punishment/CloudPlayerPunishmentManagerBridgeImpl.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/punishment/CloudPlayerPunishmentManagerBridgeImpl.kt index 1f3b0fd8..198d4f44 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/punishment/CloudPlayerPunishmentManagerBridgeImpl.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/punishment/CloudPlayerPunishmentManagerBridgeImpl.kt @@ -2,16 +2,16 @@ package dev.slne.surf.cloud.core.common.player.punishment import com.google.auto.service.AutoService import com.google.common.flogger.StackSize +import dev.slne.surf.cloud.api.common.player.OfflineCloudPlayer import dev.slne.surf.cloud.api.common.player.punishment.CloudPlayerPunishmentManagerBridge import dev.slne.surf.cloud.api.common.player.punishment.PunishmentLoginValidation import dev.slne.surf.cloud.api.common.player.punishment.type.ban.PunishmentBan +import dev.slne.surf.cloud.api.common.player.task.PrePlayerJoinTask import dev.slne.surf.cloud.api.common.util.mutableObjectListOf import dev.slne.surf.cloud.api.common.util.mutableObjectSetOf import dev.slne.surf.cloud.api.common.util.synchronize import dev.slne.surf.cloud.core.common.messages.MessageManager -import dev.slne.surf.cloud.core.common.player.CommonOfflineCloudPlayerImpl import dev.slne.surf.cloud.core.common.player.PunishmentManager -import dev.slne.surf.cloud.core.common.player.task.PrePlayerJoinTask import dev.slne.surf.cloud.core.common.util.bean import dev.slne.surf.surfapi.core.api.util.logger import dev.slne.surf.surfapi.core.api.util.toObjectList @@ -19,7 +19,7 @@ import it.unimi.dsi.fastutil.objects.ObjectList import org.jetbrains.annotations.Unmodifiable import org.springframework.beans.factory.config.BeanPostProcessor import org.springframework.context.SmartLifecycle -import org.springframework.core.OrderComparator +import org.springframework.core.annotation.AnnotationAwareOrderComparator import org.springframework.core.annotation.Order import org.springframework.stereotype.Component @@ -30,7 +30,7 @@ class CloudPlayerPunishmentManagerBridgeImpl : CloudPlayerPunishmentManagerBridg override fun registerLoginValidation(check: PunishmentLoginValidation) { if (loginValidations.contains(check)) return loginValidations.add(check) - OrderComparator.sort(loginValidations) + AnnotationAwareOrderComparator.sort(loginValidations) } fun registerLoginValidations(loginValidations: Collection) { @@ -39,7 +39,7 @@ class CloudPlayerPunishmentManagerBridgeImpl : CloudPlayerPunishmentManagerBridg this.loginValidations.add(validation) } } - OrderComparator.sort(this.loginValidations) + AnnotationAwareOrderComparator.sort(this.loginValidations) } override fun unregisterLoginValidation(check: PunishmentLoginValidation) { @@ -93,7 +93,7 @@ class CloudPlayerPunishmentManagerBridgeImpl : CloudPlayerPunishmentManagerBridg PrePlayerJoinTask { private val log = logger() - override suspend fun preJoin(player: CommonOfflineCloudPlayerImpl): PrePlayerJoinTask.Result { + override suspend fun preJoin(player: OfflineCloudPlayer): PrePlayerJoinTask.Result { val cache = punishmentManager.getCurrentLoginValidationPunishmentCache(player.uuid) if (cache == null) { log.atWarning() diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/punishment/type/mute/MutePunishmentListener.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/punishment/type/mute/MutePunishmentListener.kt index 2d470327..c9a6c42c 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/punishment/type/mute/MutePunishmentListener.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/punishment/type/mute/MutePunishmentListener.kt @@ -3,11 +3,11 @@ package dev.slne.surf.cloud.core.common.player.punishment.type.mute import dev.slne.surf.cloud.api.common.event.CloudEventHandler import dev.slne.surf.cloud.api.common.event.offlineplayer.punishment.CloudPlayerPunishEvent import dev.slne.surf.cloud.api.common.event.offlineplayer.punishment.CloudPlayerPunishmentUpdatedEvent +import dev.slne.surf.cloud.api.common.player.OfflineCloudPlayer import dev.slne.surf.cloud.api.common.player.punishment.type.mute.PunishmentMute +import dev.slne.surf.cloud.api.common.player.task.PrePlayerJoinTask import dev.slne.surf.cloud.core.common.player.CommonOfflineCloudPlayerImpl import dev.slne.surf.cloud.core.common.player.punishment.type.PunishmentMuteImpl -import dev.slne.surf.cloud.core.common.player.task.PrePlayerJoinTask -import org.springframework.context.event.EventListener import org.springframework.core.annotation.Order import org.springframework.stereotype.Component @@ -29,7 +29,8 @@ class MutePunishmentListener : PrePlayerJoinTask { mute.punishedPlayer().punishmentManager.updateCachedMute(mute) } - override suspend fun preJoin(player: CommonOfflineCloudPlayerImpl): PrePlayerJoinTask.Result { + override suspend fun preJoin(player: OfflineCloudPlayer): PrePlayerJoinTask.Result { + require(player is CommonOfflineCloudPlayerImpl) { "Player must be an instance of CommonOfflineCloudPlayerImpl" } player.punishmentManager.cacheMutes() return PrePlayerJoinTask.Result.ALLOWED } diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/task/PrePlayerJoinTaskAutoRegistrationHandler.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/task/PrePlayerJoinTaskAutoRegistrationHandler.kt new file mode 100644 index 00000000..67054d50 --- /dev/null +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/task/PrePlayerJoinTaskAutoRegistrationHandler.kt @@ -0,0 +1,35 @@ +package dev.slne.surf.cloud.core.common.player.task + +import dev.slne.surf.cloud.api.common.player.task.PrePlayerJoinTask +import dev.slne.surf.cloud.api.common.util.mutableObjectSetOf +import org.springframework.beans.factory.config.BeanPostProcessor +import org.springframework.context.SmartLifecycle +import org.springframework.stereotype.Component + +@Component +class PrePlayerJoinTaskAutoRegistrationHandler : BeanPostProcessor, SmartLifecycle { + private val watched = mutableObjectSetOf() + private var running = false + + override fun postProcessAfterInitialization(bean: Any, beanName: String): Any? { + if (bean is PrePlayerJoinTask) { + if (watched.add(bean) && running) { + PrePlayerJoinTaskManager.registerTask(bean) + } + } + return bean + } + + override fun start() { + PrePlayerJoinTaskManager.registerTasks(watched) + running = true + } + + override fun stop() { + watched.forEach { PrePlayerJoinTaskManager.unregisterTask(it) } + watched.clear() + running = false + } + + override fun isRunning() = running +} \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/task/PrePlayerJoinTaskManager.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/task/PrePlayerJoinTaskManager.kt index 8ff144df..d2e3c5d4 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/task/PrePlayerJoinTaskManager.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/task/PrePlayerJoinTaskManager.kt @@ -1,56 +1,65 @@ package dev.slne.surf.cloud.core.common.player.task -import dev.slne.surf.cloud.api.common.util.mutableObjectListOf -import dev.slne.surf.cloud.core.common.coroutines.PrePlayerJoinTaskScope -import dev.slne.surf.cloud.core.common.player.CommonCloudPlayerImpl +import dev.slne.surf.cloud.api.common.player.task.PrePlayerJoinTask import dev.slne.surf.cloud.core.common.player.CommonOfflineCloudPlayerImpl import dev.slne.surf.surfapi.core.api.util.logger import kotlinx.coroutines.withContext import org.springframework.beans.factory.SmartInitializingSingleton -import org.springframework.beans.factory.config.BeanPostProcessor -import org.springframework.core.OrderComparator +import org.springframework.core.annotation.AnnotationAwareOrderComparator import org.springframework.stereotype.Component +import java.util.concurrent.CopyOnWriteArrayList +import dev.slne.surf.cloud.core.common.coroutines.PrePlayerJoinTaskScope as TaskScope -@Component -class PrePlayerJoinTaskManager : BeanPostProcessor, SmartInitializingSingleton { - +object PrePlayerJoinTaskManager { private val log = logger() + private val tasks = CopyOnWriteArrayList() + private var singletonsInstantiated = false - val tasks = mutableObjectListOf() + fun registerTask(task: PrePlayerJoinTask) { + if (tasks.addIfAbsent(task)) { + maybeSort() + } + } - override fun postProcessAfterInitialization( - bean: Any, - beanName: String - ): Any { - if (bean is PrePlayerJoinTask) { - tasks.add(bean) + fun registerTasks(tasks: Collection) { + val added = this.tasks.addAllAbsent(tasks) + if (added > 0) { + maybeSort() } + } - return bean + fun unregisterTask(task: PrePlayerJoinTask) { + tasks.remove(task) } - override fun afterSingletonsInstantiated() { - OrderComparator.sort(tasks) + private fun maybeSort() { + if (singletonsInstantiated) { + AnnotationAwareOrderComparator.sort(tasks) + } } - suspend fun runTasks(player: CommonOfflineCloudPlayerImpl): PrePlayerJoinTask.Result = - withContext(PrePlayerJoinTaskScope.context) { - for (task in tasks) { - val result = try { - task.preJoin(player) - } catch (e: Exception) { + suspend fun runTasks(player: CommonOfflineCloudPlayerImpl) = withContext(TaskScope.context) { + for (task in tasks) { + val result = runCatching { task.preJoin(player) } + .getOrElse { e -> log.atSevere() .withCause(e) - .log("Failed to run pre player join tasks") - return@withContext PrePlayerJoinTask.Result.ERROR + .log("Failed to run pre player join task: ${task::class.simpleName} for player: ${player.uuid}") + PrePlayerJoinTask.Result.ERROR } - when (result) { - is PrePlayerJoinTask.Result.ALLOWED -> continue - else -> return@withContext result - } + if (result !is PrePlayerJoinTask.Result.ALLOWED) { + return@withContext result } + } + PrePlayerJoinTask.Result.ALLOWED + } - PrePlayerJoinTask.Result.ALLOWED + @Component + class Lifecycle : SmartInitializingSingleton { + override fun afterSingletonsInstantiated() { + singletonsInstantiated = true + maybeSort() } -} \ No newline at end of file + } +} diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/plugin/task/CloudBeforeStartTaskHandler.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/plugin/task/CloudBeforeStartTaskHandler.kt new file mode 100644 index 00000000..fd2d714e --- /dev/null +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/plugin/task/CloudBeforeStartTaskHandler.kt @@ -0,0 +1,38 @@ +package dev.slne.surf.cloud.core.common.plugin.task + +import dev.slne.surf.cloud.api.common.plugin.spring.task.CloudInitialSynchronizeTask +import dev.slne.surf.cloud.api.common.util.mutableObjectSetOf +import org.springframework.beans.factory.config.BeanPostProcessor +import org.springframework.context.SmartLifecycle +import org.springframework.stereotype.Component + +@Component +class CloudBeforeStartTaskHandler: BeanPostProcessor, SmartLifecycle { + private val watched = mutableObjectSetOf() + private var running = false + + override fun postProcessAfterInitialization(bean: Any, beanName: String): Any? { + if (bean is CloudInitialSynchronizeTask) { + watched.add(bean) + if (running) { + CloudSynchronizeTaskManager.registerTask(bean) + } + } + + return bean + } + + override fun start() { + CloudSynchronizeTaskManager.registerTasks(watched) + running = true + } + + override fun stop() { + running = false + watched.forEach { CloudSynchronizeTaskManager.unregisterTask(it) } + } + + override fun isRunning(): Boolean { + return running + } +} \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/plugin/task/CloudSynchronizeTaskManager.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/plugin/task/CloudSynchronizeTaskManager.kt new file mode 100644 index 00000000..8412b5e1 --- /dev/null +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/plugin/task/CloudSynchronizeTaskManager.kt @@ -0,0 +1,53 @@ +package dev.slne.surf.cloud.core.common.plugin.task + +import dev.slne.surf.cloud.api.common.netty.NettyClient +import dev.slne.surf.cloud.api.common.plugin.spring.task.CloudInitialSynchronizeTask +import dev.slne.surf.cloud.api.common.util.mutableObjectListOf +import dev.slne.surf.cloud.api.common.util.synchronize +import dev.slne.surf.cloud.core.common.coroutines.BeforeStartTaskScope +import dev.slne.surf.surfapi.core.api.util.freeze +import dev.slne.surf.surfapi.core.api.util.logger +import kotlinx.coroutines.launch +import org.springframework.core.annotation.AnnotationAwareOrderComparator +import kotlin.system.measureTimeMillis + +object CloudSynchronizeTaskManager { + private val log = logger() + private val _tasks = mutableObjectListOf().synchronize() + val tasks = _tasks.freeze() + + fun registerTask(task: CloudInitialSynchronizeTask) { + if (_tasks.contains(task)) return + _tasks.add(task) + AnnotationAwareOrderComparator.sort(_tasks) + } + + fun registerTasks(tasks: Collection) { + for (task in tasks) { + if (!this._tasks.contains(task)) { + this._tasks.add(task) + } + } + AnnotationAwareOrderComparator.sort(this._tasks) + } + + fun unregisterTask(task: CloudInitialSynchronizeTask) { + _tasks.remove(task) + } + + suspend fun executeTasks(client: NettyClient) { + for ((position, task) in _tasks.withIndex()) { + log.atInfo() + .log("Executing initial synchronize task: ${task.name} (${position + 1}/${_tasks.size})") + + val duration = measureTimeMillis { + BeforeStartTaskScope.launch(BeforeStartTaskScope.TaskName(task.name, position)) { + task.execute(client) + }.join() + } + + log.atInfo() + .log("Task ${task.name} executed in ${duration}ms") + } + } +} \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/processors/NettyPacketProcessor.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/processors/NettyPacketProcessor.kt index 9149922d..f0ad4807 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/processors/NettyPacketProcessor.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/processors/NettyPacketProcessor.kt @@ -8,8 +8,10 @@ import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket import dev.slne.surf.cloud.api.common.netty.packet.findPacketCodec import dev.slne.surf.cloud.api.common.netty.packet.getPacketMetaOrNull import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf +import dev.slne.surf.cloud.core.common.netty.network.ProtocolInfo import dev.slne.surf.cloud.core.common.netty.network.protocol.ProtocolInfoBuilder import dev.slne.surf.cloud.core.common.netty.network.protocol.running.RunningProtocols +import dev.slne.surf.cloud.core.common.netty.network.protocol.synchronizing.SynchronizingProtocols import dev.slne.surf.surfapi.core.api.util.logger import org.springframework.boot.autoconfigure.AutoConfigurationPackages import org.springframework.context.ApplicationContextInitializer @@ -61,7 +63,8 @@ class NettyPacketProcessor : ApplicationContextInitializer() if (codec == null) { @@ -70,6 +73,33 @@ class NettyPacketProcessor : ApplicationContextInitializer { + registerPacket( + packetMeta.flow, + RunningProtocols.CLIENTBOUND_TEMPLATE, + RunningProtocols.SERVERBOUND_TEMPLATE, + packet, + codec + ) + } + + ConnectionProtocol.SYNCHRONIZING -> { + registerPacket( + packetMeta.flow, + SynchronizingProtocols.CLIENTBOUND_TEMPLATE, + SynchronizingProtocols.SERVERBOUND_TEMPLATE, + packet, + codec + ) + } + + else -> {} + } + } + when (packetMeta.flow) { PacketFlow.CLIENTBOUND -> RunningProtocols.CLIENTBOUND_TEMPLATE.addPacket( packet.java as Class, @@ -97,6 +127,37 @@ class NettyPacketProcessor : ApplicationContextInitializer, + serverboundTemplate: ProtocolInfo.Unbound.Mutable<*, SurfByteBuf>, + packet: KClass, + codec: StreamCodec, + ) { + when (flow) { + PacketFlow.CLIENTBOUND -> clientboundTemplate.addPacket( + packet.java as Class, + codec + ) + + PacketFlow.SERVERBOUND -> serverboundTemplate.addPacket( + packet.java as Class, + codec + ) + + PacketFlow.BIDIRECTIONAL -> { + clientboundTemplate.addPacket( + packet.java as Class, + codec + ) + serverboundTemplate.addPacket( + packet.java as Class, + codec + ) + } + } + } + private fun logRegistration(packet: KClass, packetMeta: SurfNettyPacket) { log.atFine() .log(buildString { diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/server/AbstractCloudServer.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/server/AbstractCloudServer.kt index fa73c412..10aea562 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/server/AbstractCloudServer.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/server/AbstractCloudServer.kt @@ -10,14 +10,17 @@ import dev.slne.surf.cloud.api.common.util.objectListOf import dev.slne.surf.cloud.core.common.netty.network.protocol.running.ClientInformation import it.unimi.dsi.fastutil.objects.ObjectList import kotlinx.coroutines.awaitAll +import java.net.InetSocketAddress abstract class AbstractCloudServer( uid: Long, group: String, name: String, + playAddress: InetSocketAddress, + override val lobby: Boolean, users: UserListImpl = UserListImpl(), information: ClientInformation = ClientInformation.NOT_AVAILABLE -) : CommonCloudServerImpl(uid, group, name, users, information), CloudServer { +) : CommonCloudServerImpl(uid, group, name, users, playAddress, information), CloudServer { override val allowlist get() = information.allowlist override suspend fun pullPlayers(players: Collection): ObjectList> { diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/server/AbstractProxyCloudServer.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/server/AbstractProxyCloudServer.kt index c55a4220..ee7d9aa1 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/server/AbstractProxyCloudServer.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/server/AbstractProxyCloudServer.kt @@ -3,11 +3,13 @@ package dev.slne.surf.cloud.core.common.server import dev.slne.surf.cloud.api.common.server.ProxyCloudServer import dev.slne.surf.cloud.api.common.server.UserListImpl import dev.slne.surf.cloud.core.common.netty.network.protocol.running.ClientInformation +import java.net.InetSocketAddress abstract class AbstractProxyCloudServer( uid: Long, group: String, name: String, + playAddress: InetSocketAddress, users: UserListImpl = UserListImpl(), information: ClientInformation = ClientInformation.NOT_AVAILABLE -) : CommonCloudServerImpl(uid, group, name, users, information), ProxyCloudServer \ No newline at end of file +) : CommonCloudServerImpl(uid, group, name, users, playAddress, information), ProxyCloudServer \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/server/CommonCloudServerImpl.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/server/CommonCloudServerImpl.kt index a6da0a22..b568d153 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/server/CommonCloudServerImpl.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/server/CommonCloudServerImpl.kt @@ -15,12 +15,14 @@ import kotlinx.coroutines.async import kotlinx.coroutines.delay import net.kyori.adventure.sound.Sound.Emitter import net.kyori.adventure.text.Component +import java.net.InetSocketAddress abstract class CommonCloudServerImpl( override val uid: Long, override val group: String, override val name: String, override val users: UserListImpl, + override val playAddress: InetSocketAddress, @Volatile var information: ClientInformation diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/server/CommonCloudServerManagerImpl.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/server/CommonCloudServerManagerImpl.kt index b2cbf317..17447f9f 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/server/CommonCloudServerManagerImpl.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/server/CommonCloudServerManagerImpl.kt @@ -3,30 +3,28 @@ package dev.slne.surf.cloud.core.common.server import dev.slne.surf.cloud.api.common.server.CloudServer import dev.slne.surf.cloud.api.common.server.CloudServerManager import dev.slne.surf.cloud.api.common.server.CommonCloudServer +import dev.slne.surf.cloud.api.common.server.ProxyCloudServer +import dev.slne.surf.cloud.api.common.util.* import dev.slne.surf.cloud.api.common.util.annotation.InternalApi -import dev.slne.surf.cloud.api.common.util.mutableLong2ObjectMapOf -import dev.slne.surf.cloud.api.common.util.mutableObjectListOf -import dev.slne.surf.cloud.api.common.util.synchronize -import dev.slne.surf.cloud.api.common.util.toObjectSet import it.unimi.dsi.fastutil.objects.ObjectCollection import it.unimi.dsi.fastutil.objects.ObjectList import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import net.kyori.adventure.text.Component -abstract class CommonCloudServerManagerImpl : CloudServerManager { - protected val servers = mutableLong2ObjectMapOf().synchronize() +abstract class CommonCloudServerManagerImpl : CloudServerManager { + protected val servers = mutableLong2ObjectMapOf().synchronize() protected val serversMutex = Mutex() - open suspend fun registerServer(server: S) = + open suspend fun registerServer(server: CommonServer) = serversMutex.withLock { servers[server.uid] = server } - open suspend fun unregisterServer(uid: Long) = - serversMutex.withLock { servers.remove(uid) } + open suspend fun unregisterServer(uid: Long): CommonServer? = + serversMutex.withLock { servers.remove(uid) } - fun getServerByIdUnsafe(uid: Long): S? = servers[uid] + fun getServerByIdUnsafe(uid: Long): CommonServer? = servers[uid] - override suspend fun retrieveServerById(id: Long): S? = serversMutex.withLock { servers[id] } + override suspend fun retrieveServerById(id: Long): CommonServer? = serversMutex.withLock { servers[id] } override suspend fun retrieveServerByCategoryAndName( category: String, @@ -48,7 +46,7 @@ abstract class CommonCloudServerManagerImpl : CloudServer .filter { it.name.equals(name, ignoreCase = true) } .minByOrNull { it.currentPlayerCount } as? CloudServer - override suspend fun retrieveServersInGroup(group: String): ObjectList = + override suspend fun retrieveServersInGroup(group: String): ObjectList = serversMutex.withLock { servers.values.filterTo(mutableObjectListOf()) { it.isInGroup(group) } } @@ -64,14 +62,22 @@ abstract class CommonCloudServerManagerImpl : CloudServer suspend fun batchUpdateServer(update: List) { serversMutex.withLock { - update.forEach { servers[it.uid] = it as S } + update.forEach { servers[it.uid] = it as CommonServer } } } - override suspend fun retrieveAllServers(): ObjectCollection { + override suspend fun retrieveAllServers(): ObjectCollection { return serversMutex.withLock { servers.values.toObjectSet() } } + override suspend fun retrieveServers(): ObjectCollection { + return serversMutex.withLock { servers.values.filterIsInstanceTo(mutableObjectSetOf()) }.freeze() + } + + override suspend fun retrieveProxies(): ObjectCollection { + return serversMutex.withLock { servers.values.filterIsInstanceTo(mutableObjectSetOf()) }.freeze() + } + override suspend fun broadcastToGroup(group: String, message: Component, permission: String?, playSound: Boolean) = serversMutex.withLock { servers.values.filter { it.isInGroup(group) }.filterIsInstance() diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/spring/CloudLifecycleAware.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/spring/CloudLifecycleAware.kt index af552eb3..d02fce04 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/spring/CloudLifecycleAware.kt +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/spring/CloudLifecycleAware.kt @@ -15,5 +15,7 @@ interface CloudLifecycleAware { const val KTOR_SERVER_PRIORITY = 750 const val PLUGIN_MANAGER_PRIORITY = 700 const val NETTY_MANAGER_PRIORITY = 500 + const val BEFORE_NETTY_MANAGER = NETTY_MANAGER_PRIORITY - 50 + const val BEFORE_START_TASK_PRIORITY = 400 } } \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/sync/BasicSyncValue.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/sync/BasicSyncValue.kt new file mode 100644 index 00000000..7fe937ee --- /dev/null +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/sync/BasicSyncValue.kt @@ -0,0 +1,64 @@ +package dev.slne.surf.cloud.core.common.sync + +import dev.slne.surf.cloud.api.common.netty.network.codec.StreamCodec +import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf +import dev.slne.surf.cloud.api.common.sync.SyncValue +import dev.slne.surf.cloud.api.common.sync.SyncValueListener +import dev.slne.surf.surfapi.core.api.util.logger +import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.atomic.AtomicReference +import kotlin.time.Duration + +open class BasicSyncValue internal constructor( + override val id: String, + defaultValue: T, + override val codec: StreamCodec +) : SyncValue { + private val value = AtomicReference(defaultValue) + private val listeners = CopyOnWriteArrayList>() + + init { + CommonSyncRegistryImpl.instance.register(this) + } + + override fun get(): T = value.get() + + override fun set(newValue: T) { + val oldValue = value.getAndSet(newValue) + if (oldValue != newValue) { + callListeners(oldValue, newValue) + CommonSyncRegistryImpl.instance.afterChange(this) + } + } + + override fun subscribe(listener: SyncValueListener) = listeners.addIfAbsent(listener) + override fun rateLimited(minInterval: Duration): SyncValue = + RateLimitedSyncValue(this, minInterval) + + + @Suppress("UNCHECKED_CAST") + fun internalSet(newValue: Any?) { + val castedValue = newValue as? T + ?: error("Cannot cast value '$newValue' to type '${value.get()::class.simpleName}'") + val oldValue = value.getAndSet(castedValue) + if (oldValue != castedValue) { + callListeners(oldValue, castedValue) + } + } + + private fun callListeners(oldValue: T, newValue: T) { + for (handler in listeners) { + try { + handler(oldValue, newValue) + } catch (e: Exception) { + log.atWarning() + .withCause(e) + .log("Error while notifying listener for SyncValue '$id'") + } + } + } + + companion object { + private val log = logger() + } +} \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/sync/CommonSyncRegistryImpl.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/sync/CommonSyncRegistryImpl.kt new file mode 100644 index 00000000..337fc5f5 --- /dev/null +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/sync/CommonSyncRegistryImpl.kt @@ -0,0 +1,89 @@ +package dev.slne.surf.cloud.core.common.sync + +import dev.slne.surf.cloud.api.common.netty.network.codec.StreamCodec +import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf +import dev.slne.surf.cloud.api.common.sync.SyncRegistry +import dev.slne.surf.cloud.api.common.sync.SyncSet +import dev.slne.surf.cloud.api.common.sync.SyncValue +import dev.slne.surf.cloud.api.common.util.mutableObject2ObjectMapOf +import javax.annotation.OverridingMethodsMustInvokeSuper + +abstract class CommonSyncRegistryImpl : SyncRegistry { + protected var frozen = false + private set + + protected val syncValues = mutableObject2ObjectMapOf>() + protected val syncSets = mutableObject2ObjectMapOf>() + + fun freeze() { + require(!frozen) { "SyncRegistry is already frozen" } + frozen = true + } + + fun register(syncValue: BasicSyncValue<*>) { + require(!frozen) { "SyncRegistry is frozen and cannot accept new SyncValues" } + + val previous = syncValues.put(syncValue.id, syncValue) + check(previous == null) { "SyncValue with id '${syncValue.id}' was already registered" } + } + + fun register(syncSet: SyncSetImpl) { + require(!frozen) { "SyncRegistry is frozen and cannot accept new SyncSets" } + + val previous = syncSets.put(syncSet.id, syncSet) + check(previous == null) { "SyncSet with id '${syncSet.id}' was already registered" } + } + + @OverridingMethodsMustInvokeSuper + open fun afterChange(syncValue: BasicSyncValue<*>) { + require(frozen) { "SyncRegistry is not frozen, cannot process afterChange" } + require(syncValue.id in syncValues) { "SyncValue with id '${syncValue.id}' is not registered" } + } + + @OverridingMethodsMustInvokeSuper + open fun afterChange( + syncSet: SyncSetImpl, + added: Boolean, + changeId: Long, + element: T + ) { + require(frozen) { "SyncRegistry is not frozen, cannot process afterChange" } + require(syncSet.id in syncSets) { "SyncSet with id '${syncSet.id}' is not registered" } + } + + fun getSyncValueCodec(syncId: String): StreamCodec? { + require(frozen) { "SyncRegistry is not frozen, cannot get codec" } + + val syncValue = syncValues[syncId] ?: return null + return syncValue.codec as? StreamCodec + } + + @Suppress("UNCHECKED_CAST") + fun getSet(id: String): SyncSetImpl? = syncSets[id] as? SyncSetImpl + + fun updateSyncValue(syncId: String, value: Any?) { + require(frozen) { "SyncRegistry is not frozen, cannot update SyncValue" } + + val syncValue = syncValues[syncId] ?: error("SyncValue with id '$syncId' is not registered") + syncValue.internalSet(value) + } + + override fun createSyncValue( + id: String, + defaultValue: T, + codec: StreamCodec + ): SyncValue { + return BasicSyncValue(id, defaultValue, codec) + } + + override fun createSyncSet( + id: String, + codec: StreamCodec + ): SyncSet { + return SyncSetImpl(id, codec) + } + + companion object { + val instance by lazy { SyncRegistry.instance as CommonSyncRegistryImpl } + } +} diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/sync/RateLimitedSyncValue.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/sync/RateLimitedSyncValue.kt new file mode 100644 index 00000000..4a1ae68f --- /dev/null +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/sync/RateLimitedSyncValue.kt @@ -0,0 +1,31 @@ +package dev.slne.surf.cloud.core.common.sync + +import dev.slne.surf.cloud.api.common.sync.SyncValue +import dev.slne.surf.cloud.core.common.coroutines.SyncValueScope +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.launch +import kotlin.time.Duration + +@OptIn(FlowPreview::class) +class RateLimitedSyncValue internal constructor( + private val delegate: BasicSyncValue, + minDuration: Duration +) : SyncValue by delegate { + private val updates = MutableStateFlow(delegate.get()) + + init { + SyncValueScope.launch { + updates + .debounce(minDuration) + .collect { value -> + delegate.set(value) + } + } + } + + override fun set(newValue: T) { + updates.value = newValue + } +} \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/sync/SyncRegistryFreezeHandler.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/sync/SyncRegistryFreezeHandler.kt new file mode 100644 index 00000000..703373bc --- /dev/null +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/sync/SyncRegistryFreezeHandler.kt @@ -0,0 +1,16 @@ +package dev.slne.surf.cloud.core.common.sync + +import dev.slne.surf.cloud.api.common.util.TimeLogger +import dev.slne.surf.cloud.core.common.spring.CloudLifecycleAware +import org.springframework.core.annotation.Order +import org.springframework.stereotype.Component + +@Component +@Order(CloudLifecycleAware.BEFORE_START_TASK_PRIORITY) +class SyncRegistryFreezeHandler: CloudLifecycleAware { + override suspend fun onEnable(timeLogger: TimeLogger) { + timeLogger.measureStep("Freezing SyncRegistry") { + CommonSyncRegistryImpl.instance.freeze() + } + } +} \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/sync/SyncSetImpl.kt b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/sync/SyncSetImpl.kt new file mode 100644 index 00000000..35fd11e8 --- /dev/null +++ b/surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/sync/SyncSetImpl.kt @@ -0,0 +1,110 @@ +package dev.slne.surf.cloud.core.common.sync + +import dev.slne.surf.cloud.api.common.netty.network.codec.StreamCodec +import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf +import dev.slne.surf.cloud.api.common.sync.SyncSet +import dev.slne.surf.cloud.api.common.sync.SyncSetListener +import dev.slne.surf.cloud.api.common.util.mutableObjectSetOf +import dev.slne.surf.cloud.api.common.util.toObjectSet +import dev.slne.surf.surfapi.core.api.util.logger +import it.unimi.dsi.fastutil.objects.ObjectIterator +import it.unimi.dsi.fastutil.objects.ObjectSet +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.atomic.AtomicLong +import java.util.function.Consumer + +class SyncSetImpl(override val id: String, val valueCodec: StreamCodec) : + SyncSet { + override val codec: StreamCodec> = StreamCodec.of( + { buf, value -> + buf.writeCollection(value) { buf, item -> valueCodec.encode(buf, item) } + }, + { buf -> + buf.readCollection({ mutableObjectSetOf(it) }) { buf -> valueCodec.decode(buf) } + } + ) + + private val backing = ConcurrentHashMap.newKeySet() + private val listeners = CopyOnWriteArrayList>() + private val changeCounter = AtomicLong() + + + init { + CommonSyncRegistryImpl.instance.register(this) + } + + override fun subscribe(listener: SyncSetListener) = listeners.addIfAbsent(listener) + + fun addInternal(element: T) = backing.add(element) + fun removeInternal(element: T) = backing.remove(element) + fun addAllInternal(elements: Collection) = backing.addAll(elements) + + override fun add(element: T) = backing.add(element).also { added -> + if (added) fireDelta(true, element) + } + + override fun remove(element: T) = backing.remove(element).also { removed -> + if (removed) fireDelta(false, element) + } + + private fun fireDelta(added: Boolean, element: T) { + val changeId = changeCounter.incrementAndGet() + callListeners(added, element) + CommonSyncRegistryImpl.instance.afterChange(this, added, changeId, element) + } + + private fun callListeners(added: Boolean, element: T) { + for (listener in listeners) { + try { + listener(added, element) + } catch (e: Exception) { + log.atWarning() + .withCause(e) + .log("Error while notifying listener for SyncSet '$id'") + } + } + } + + override val size: Int get() = backing.size + override fun contains(element: T) = backing.contains(element) + override fun containsAll(elements: Collection) = backing.containsAll(elements) + override fun isEmpty() = backing.isEmpty() + override fun addAll(elements: Collection) = elements.map(::add).any { it } + override fun retainAll(elements: Collection) = + backing.filterNot { it in elements }.any { remove(it) } + + + override fun removeAll(elements: Collection) = elements.map(::remove).any { it } + + override fun clear() = backing.toList().forEach { remove(it) } + override fun iterator(): ObjectIterator = SyncSetIterator() + override fun snapshot(): ObjectSet = backing.toObjectSet() + + private inner class SyncSetIterator : ObjectIterator { + private val iterator = backing.iterator() + private var last: T? = null + + override fun hasNext() = iterator.hasNext() + + override fun next(): T { + val nextElement = iterator.next() + last = nextElement + return nextElement + } + + override fun remove() { + val elementToRemove = last ?: error("next() must be called before remove()") + this@SyncSetImpl.remove(elementToRemove) + last = null + } + + override fun forEachRemaining(action: Consumer) { + iterator.forEachRemaining(action) + } + } + + companion object { + private val log = logger() + } +} \ No newline at end of file diff --git a/surf-cloud-core/surf-cloud-core-common/src/main/resources/application.properties b/surf-cloud-core/surf-cloud-core-common/src/main/resources/application.properties index 5974c924..05cf2858 100644 --- a/surf-cloud-core/surf-cloud-core-common/src/main/resources/application.properties +++ b/surf-cloud-core/surf-cloud-core-common/src/main/resources/application.properties @@ -13,3 +13,5 @@ spring.jpa.properties.hibernate.cache.use_query_cache=true spring.jpa.properties.hibernate.cache.region.factory_class=jcache spring.jpa.properties.hibernate.javax.cache.provider=org.ehcache.jsr107.EhcacheCachingProvider spring.jpa.properties.hibernate.javax.cache.missing_cache_strategy=create + +spring.aop.proxy-target-class=false \ No newline at end of file diff --git a/surf-cloud-standalone-launcher/build.gradle.kts b/surf-cloud-standalone-launcher/build.gradle.kts index d30c4815..cdfb1d04 100644 --- a/surf-cloud-standalone-launcher/build.gradle.kts +++ b/surf-cloud-standalone-launcher/build.gradle.kts @@ -1,6 +1,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootJar plugins { + `exclude-kotlin` id("dev.slne.surf.surfapi.gradle.standalone") application diff --git a/surf-cloud-standalone-launcher/src/main/java/dev/slne/surf/cloud/launcher/LauncherAgent.java b/surf-cloud-standalone-launcher/src/main/java/dev/slne/surf/cloud/launcher/LauncherAgent.java index ebb1bc88..dbc472e2 100644 --- a/surf-cloud-standalone-launcher/src/main/java/dev/slne/surf/cloud/launcher/LauncherAgent.java +++ b/surf-cloud-standalone-launcher/src/main/java/dev/slne/surf/cloud/launcher/LauncherAgent.java @@ -1,8 +1,23 @@ package dev.slne.surf.cloud.launcher; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.lang.instrument.Instrumentation; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.regex.Pattern; +import org.springframework.instrument.InstrumentationSavingAgent; public class LauncherAgent { + private static Instrumentation instrumentation; public static void agentmain(String agentArgs, Instrumentation inst) { premain(agentArgs, inst); @@ -10,6 +25,83 @@ public static void agentmain(String agentArgs, Instrumentation inst) { public static void premain(String agentArgs, Instrumentation inst) { System.out.println("Launcher-Agent started. Loading actual agent..."); -// InstrumentationSavingAgent.premain(agentArgs, inst); + InstrumentationSavingAgent.premain(agentArgs, inst); + instrumentation = inst; +// launchSparkHook(agentArgs, inst); + } + + private static void launchSparkHook(String agentArgs, Instrumentation inst) { + System.out.println("SparkHook started. Loading agent..."); + try { + final String regex = ".*/spark-.*-standalone-agent\\.jara$"; + final String resourcePath = findResourceByRegex(regex); + + if (resourcePath == null) { + throw new FileNotFoundException("Spark agent resource not found: " + regex); + } + + final File agentJar = extractAgentJar(resourcePath); + + try (final URLClassLoader cl = new URLClassLoader(new URL[]{agentJar.toURI().toURL()}, LauncherAgent.class.getClassLoader())) { + final ClassLoader currentContextClassloader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(cl); + + final Class agentClass = cl.loadClass( + "me.lucko.spark.standalone.StandaloneSparkAgent"); + final Method premain = agentClass.getMethod("premain", String.class, + Instrumentation.class); + premain.invoke(null, agentArgs, inst); + + Thread.currentThread().setContextClassLoader(currentContextClassloader); + } + } catch (Exception e) { + throw new Error(e); + } + } + + private static String findResourceByRegex(String regex) throws IOException { + final String jarPath = LauncherAgent.class + .getProtectionDomain() + .getCodeSource() + .getLocation() + .getPath(); + + final Pattern pattern = Pattern.compile(regex); + + try (final JarFile jarFile = new JarFile(jarPath)) { + final Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + final JarEntry entry = entries.nextElement(); + final String name = "/" + entry.getName(); + if (pattern.matcher(name).matches()) { + return name; + } + } + } + return null; + } + + private static File extractAgentJar(String resourcePath) throws IOException { + try (final InputStream in = LauncherAgent.class.getResourceAsStream(resourcePath)) { + if (in == null) { + throw new FileNotFoundException("Resource not found: " + resourcePath); + } + + final File tempFile = File.createTempFile("spark-agent-", ".jar"); + tempFile.deleteOnExit(); + + try (final OutputStream out = new FileOutputStream(tempFile)) { + byte[] buffer = new byte[4096]; + int len; + while ((len = in.read(buffer)) > 0) { + out.write(buffer, 0, len); + } + } + return tempFile; + } + } + + public static Instrumentation getInstrumentation() { + return instrumentation; } } diff --git a/surf-cloud-standalone-launcher/src/main/java/dev/slne/surf/cloud/launcher/StandaloneUrlClassLoader.java b/surf-cloud-standalone-launcher/src/main/java/dev/slne/surf/cloud/launcher/StandaloneUrlClassLoader.java index 94961e29..a2ef911b 100644 --- a/surf-cloud-standalone-launcher/src/main/java/dev/slne/surf/cloud/launcher/StandaloneUrlClassLoader.java +++ b/surf-cloud-standalone-launcher/src/main/java/dev/slne/surf/cloud/launcher/StandaloneUrlClassLoader.java @@ -8,6 +8,7 @@ public class StandaloneUrlClassLoader extends URLClassLoader { private static final String INSTRUMENTATION_SAVING_AGENT = "org.springframework.instrument.InstrumentationSavingAgent"; + private static final String LAUNCHER_AGENT = "dev.slne.surf.cloud.launcher.LauncherAgent"; public StandaloneUrlClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent); @@ -27,6 +28,8 @@ protected Class findClass(String name) throws ClassNotFoundException { System.out.println("Loading InstrumentationSavingAgent"); } + return Class.forName(name, false, Main.class.getClassLoader()); + } else if (LAUNCHER_AGENT.equals(name)) { return Class.forName(name, false, Main.class.getClassLoader()); } diff --git a/surf-cloud-standalone/build.gradle.kts b/surf-cloud-standalone/build.gradle.kts index 33243a17..4676fba3 100644 --- a/surf-cloud-standalone/build.gradle.kts +++ b/surf-cloud-standalone/build.gradle.kts @@ -1,24 +1,23 @@ -import java.util.Properties +import java.util.* plugins { id("dev.slne.surf.surfapi.gradle.standalone") alias(libs.plugins.spring.boot) } +repositories { + maven("https://jitpack.io") +} + dependencies { api(project(":surf-cloud-core:surf-cloud-core-common")) api(project(":surf-cloud-api:surf-cloud-api-server")) runtimeOnly(libs.mariadb.java.client) runtimeOnly(libs.mysql.connector.j) - api(libs.spring.boot.starter.data.jpa) api(libs.reactive.streams) api(libs.velocity.native) - implementation(libs.hibernate.jcache) - implementation(libs.ehcache) - - // Ktor implementation(libs.ktor.server.status.pages) @@ -31,6 +30,11 @@ dependencies { ) } } + +// implementation(fileTree("libs/**/*.jar")) // Include all JARs in libs directory + implementation(fileTree("libs") { + include("*.jar") + }) } tasks { diff --git a/surf-cloud-standalone/libs/spark-1.10.138-standalone-agent.jar b/surf-cloud-standalone/libs/spark-1.10.138-standalone-agent.jar new file mode 100644 index 00000000..4b8d6df0 Binary files /dev/null and b/surf-cloud-standalone/libs/spark-1.10.138-standalone-agent.jar differ diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/InstrumentationProvider.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/InstrumentationProvider.kt new file mode 100644 index 00000000..329be1c2 --- /dev/null +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/InstrumentationProvider.kt @@ -0,0 +1,26 @@ +package dev.slne.surf.cloud.standalone + +import java.lang.instrument.Instrumentation +import java.lang.invoke.MethodHandle +import java.lang.invoke.MethodHandles +import java.lang.invoke.MethodType + +object InstrumentationProvider { + private val classLoader: ClassLoader = javaClass.classLoader.parent + private val getInstrumentationMethodHandle: MethodHandle + + init { + val lookup = MethodHandles.lookup() + val launcherAgentClass = + Class.forName("dev.slne.surf.cloud.launcher.LauncherAgent", true, classLoader) + getInstrumentationMethodHandle = lookup.findStatic( + launcherAgentClass, "getInstrumentation", MethodType.methodType( + Instrumentation::class.java + ) + ) + } + + fun getInstrumentation(): Instrumentation { + return getInstrumentationMethodHandle.invokeExact() as Instrumentation + } +} \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/__AdditionalSpringStandaloneConfiguration.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/__AdditionalSpringStandaloneConfiguration.kt index 29caecff..563237d0 100644 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/__AdditionalSpringStandaloneConfiguration.kt +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/__AdditionalSpringStandaloneConfiguration.kt @@ -4,7 +4,6 @@ import dev.slne.surf.cloud.api.server.plugin.AdditionalStandaloneConfiguration import dev.slne.surf.cloud.api.server.plugin.TransactionConfiguration import org.jetbrains.exposed.spring.autoconfigure.ExposedAutoConfiguration import org.springframework.boot.autoconfigure.ImportAutoConfiguration -import org.springframework.context.annotation.EnableAspectJAutoProxy import org.springframework.context.annotation.Import /** @@ -15,5 +14,5 @@ import org.springframework.context.annotation.Import @AdditionalStandaloneConfiguration @ImportAutoConfiguration(ExposedAutoConfiguration::class) @Import(TransactionConfiguration::class) -@EnableAspectJAutoProxy(proxyTargetClass = true) +//@EnableAspectJAutoProxy(proxyTargetClass = true) internal class __AdditionalSpringStandaloneConfiguration diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/commands/ArgumentSuggestion.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/commands/ArgumentSuggestion.kt new file mode 100644 index 00000000..900bce6b --- /dev/null +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/commands/ArgumentSuggestion.kt @@ -0,0 +1,40 @@ +package dev.slne.surf.cloud.standalone.commands + +import com.google.common.base.CharMatcher +import com.mojang.brigadier.suggestion.Suggestions +import com.mojang.brigadier.suggestion.SuggestionsBuilder +import java.util.concurrent.CompletableFuture + +object ArgumentSuggestion { + + val MATCH_SPLITTER = CharMatcher.anyOf("._/") + + fun suggestStrings( + strings: Iterable, + builder: SuggestionsBuilder + ): CompletableFuture { + val remaining = builder.remainingLowerCase + + for (string in strings) { + if (matchesSubstring(remaining, string.lowercase())) { + builder.suggest(string) + } + } + + return builder.buildFuture() + } + + fun matchesSubstring(input: String, substring: String): Boolean { + var startIndex = 0 + + while (!substring.startsWith(input, startIndex)) { + val nextSplitIndex = MATCH_SPLITTER.indexIn(substring, startIndex) + if (nextSplitIndex < 0) { + return false + } + startIndex = nextSplitIndex + 1 + } + + return true + } +} \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/commands/CommandManagerImpl.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/commands/CommandManagerImpl.kt index f5458e8a..c9d8fe27 100644 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/commands/CommandManagerImpl.kt +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/commands/CommandManagerImpl.kt @@ -9,6 +9,7 @@ import dev.slne.surf.cloud.api.server.command.CommandResultCallback import dev.slne.surf.cloud.api.server.command.CommandSource import dev.slne.surf.cloud.standalone.commands.execution.ExecutionContext import dev.slne.surf.cloud.standalone.commands.impl.ShutdownCommand +import dev.slne.surf.cloud.standalone.commands.impl.SparkCommand import dev.slne.surf.cloud.standalone.commands.impl.TestCommand import dev.slne.surf.surfapi.core.api.messages.Colors import dev.slne.surf.surfapi.core.api.messages.adventure.appendText @@ -28,6 +29,7 @@ class CommandManagerImpl { init { ShutdownCommand.register(dispatcher) TestCommand.register(dispatcher) + SparkCommand.register(dispatcher) } fun createCommandSource(): CommandSource { @@ -56,7 +58,6 @@ class CommandManagerImpl { val contextChain = finishParsing(parseresults, input, source, label) ?: return try { - println("Executing command in context") executeCommandInContext { ExecutionContext.queueInitialCommandExecution( it, diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/commands/execution/ExecutionContext.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/commands/execution/ExecutionContext.kt index 01fee369..a489262e 100644 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/commands/execution/ExecutionContext.kt +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/commands/execution/ExecutionContext.kt @@ -87,7 +87,6 @@ class ExecutionContext( source: T, callback: CommandResultCallback, ) { - println("Queueing initial command execution") context.queueNext( CommandQueueEntry( context.createTopFrame(callback), diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/commands/impl/SparkCommand.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/commands/impl/SparkCommand.kt new file mode 100644 index 00000000..8a360e2f --- /dev/null +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/commands/impl/SparkCommand.kt @@ -0,0 +1,49 @@ +package dev.slne.surf.cloud.standalone.commands.impl + +import com.mojang.brigadier.Command +import com.mojang.brigadier.CommandDispatcher +import com.mojang.brigadier.arguments.StringArgumentType +import com.mojang.brigadier.suggestion.SuggestionProvider +import dev.slne.surf.cloud.api.server.command.CommandSource +import dev.slne.surf.cloud.api.server.command.ConsoleCommand +import dev.slne.surf.cloud.api.server.command.literal +import dev.slne.surf.cloud.api.server.command.then +import dev.slne.surf.cloud.core.common.util.bean +import dev.slne.surf.cloud.standalone.commands.ArgumentSuggestion +import dev.slne.surf.cloud.standalone.spark.SparkHook +import me.lucko.spark.standalone.StandaloneCommandSender + +object SparkCommand : ConsoleCommand { + private val sparkHook by lazy { bean() } + + private val suggestionProvider = SuggestionProvider { context, builder -> + val input = builder.remainingLowerCase + val resultBuilder = if (input.contains(" ")) builder.createOffset( + builder.start + input.substringBeforeLast(" ").length + 1 + ) else builder + val suggestions = sparkHook.spark.suggest( + input.split(" ").toTypedArray(), + StandaloneCommandSender.SYSTEM_OUT + ) + ArgumentSuggestion.suggestStrings(suggestions, resultBuilder) + } + + fun register(dispatcher: CommandDispatcher) { + dispatcher.register(literal("spark") { + then("input", StringArgumentType.greedyString()) { + executes { context -> + val input = StringArgumentType.getString(context, "input") + val sparkHook = bean() + sparkHook.spark.execute( + input.split(" ").toTypedArray(), + StandaloneCommandSender.SYSTEM_OUT + ) + Command.SINGLE_SUCCESS + } + + + suggests(suggestionProvider) + } + }) + } +} \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/config/StandaloneConfig.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/config/StandaloneConfig.kt index 5c93e0f5..e8a71040 100644 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/config/StandaloneConfig.kt +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/config/StandaloneConfig.kt @@ -36,6 +36,9 @@ data class StandaloneConfig( @Comment("Configuration for the queue system.") @Setting("queue") val queue: QueueConfig = QueueConfig(), + + @Setting("proxy") + val proxy: ProxyConfig = ProxyConfig(), ) @ConfigSerializable @@ -82,3 +85,20 @@ data class QueueConfig( val suspendedQueueCharacter: Char = '⏸', ) + +@ConfigSerializable +data class ProxyConfig( + @Setting("secret") + val secretConfig: SecretConfig = SecretConfig()) +{ + @ConfigSerializable + data class SecretConfig( + val type: SecretType = SecretType.DYNAMIC, + val manualSecret: String = "", + ) { + enum class SecretType { + MANUAL, + DYNAMIC + } + } +} diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/NettyServerImpl.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/NettyServerImpl.kt index 365ba0df..215243fc 100644 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/NettyServerImpl.kt +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/NettyServerImpl.kt @@ -9,6 +9,8 @@ import dev.slne.surf.cloud.standalone.netty.server.connection.ServerConnectionLi import dev.slne.surf.cloud.standalone.server.StandaloneCloudServerImpl import dev.slne.surf.cloud.standalone.server.StandaloneProxyCloudServerImpl import dev.slne.surf.cloud.standalone.server.serverManagerImpl +import dev.slne.surf.cloud.standalone.spark.provider.CloudTickHook +import dev.slne.surf.cloud.standalone.spark.provider.CloudTickReporter import dev.slne.surf.surfapi.core.api.util.logger import io.netty.channel.epoll.Epoll import io.netty.channel.unix.DomainSocketAddress @@ -26,6 +28,7 @@ import java.net.InetAddress import java.net.InetSocketAddress import java.net.SocketAddress import java.util.concurrent.TimeUnit +import kotlin.system.measureTimeMillis @Component @Profile("server") @@ -106,12 +109,17 @@ class NettyServerImpl : InitializingBean, DisposableBean { @Scheduled(fixedRate = 1, timeUnit = TimeUnit.SECONDS) protected suspend fun tick() { if (!running) return - connection.connections.forEach { it.tick() } - connection.tick() - schedules.removeAll { function -> - function() - true + + val duration = measureTimeMillis { + CloudTickHook.tick() + connection.connections.forEach { it.tick() } + connection.tick() + schedules.removeAll { function -> + function() + true + } } + CloudTickReporter.tick(duration.toDouble()) } fun schedule(function: () -> Unit) { @@ -142,13 +150,16 @@ class NettyServerImpl : InitializingBean, DisposableBean { client.serverId, client.serverCategory, client.serverName, - client.connection + client.playAddress, + client.connection, ) } else { StandaloneCloudServerImpl( client.serverId, client.serverCategory, client.serverName, + client.playAddress, + client.lobbyServer, client.connection ) } diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/ProxySecretHolder.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/ProxySecretHolder.kt new file mode 100644 index 00000000..e10baf6f --- /dev/null +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/ProxySecretHolder.kt @@ -0,0 +1,27 @@ +package dev.slne.surf.cloud.standalone.netty.server + +import dev.slne.surf.cloud.api.server.netty.packet.broadcast +import dev.slne.surf.cloud.core.common.netty.network.protocol.common.ClientboundSetVelocitySecretPacket +import dev.slne.surf.cloud.standalone.config.ProxyConfig.SecretConfig.SecretType +import dev.slne.surf.cloud.standalone.config.standaloneConfig +import org.apache.commons.lang3.RandomStringUtils + +object ProxySecretHolder { + + private var dynamicSecret = randomSecret() + + fun currentSecret(): ByteArray { + val secretConfig = standaloneConfig.proxy.secretConfig + return when (secretConfig.type) { + SecretType.MANUAL -> secretConfig.manualSecret.toByteArray() + SecretType.DYNAMIC -> dynamicSecret + } + } + + fun reloadSecret() { + dynamicSecret = randomSecret() + ClientboundSetVelocitySecretPacket(currentSecret()).broadcast() + } + + private fun randomSecret() = RandomStringUtils.secureStrong().next(128).toByteArray() +} \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/ProxyServerAutoregistration.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/ProxyServerAutoregistration.kt index aac1001b..32382d23 100644 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/ProxyServerAutoregistration.kt +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/ProxyServerAutoregistration.kt @@ -1,32 +1,13 @@ package dev.slne.surf.cloud.standalone.netty.server -import dev.slne.surf.cloud.api.common.util.mutableObjectListOf -import dev.slne.surf.cloud.api.common.util.synchronize import dev.slne.surf.cloud.standalone.server.StandaloneCloudServerImpl import dev.slne.surf.cloud.standalone.server.StandaloneProxyCloudServerImpl object ProxyServerAutoregistration { - private val pendingClients = mutableObjectListOf().synchronize() - private var proxy: StandaloneProxyCloudServerImpl? = null - - val hasProxy get() = proxy != null - fun registerClient(client: StandaloneCloudServerImpl) { - if (proxy == null) { - pendingClients.add(client) - return - } - proxy!!.registerClients(client) } fun setProxy(proxy: StandaloneProxyCloudServerImpl) { - this.proxy = proxy - proxy.registerClients(*pendingClients.toTypedArray()) - pendingClients.clear() - } - - fun clearProxy() { - proxy = null } } \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/ServerClientImpl.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/ServerClientImpl.kt index a58ae317..0a5398b7 100644 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/ServerClientImpl.kt +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/ServerClientImpl.kt @@ -4,24 +4,24 @@ import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket import dev.slne.surf.cloud.core.common.netty.CommonNettyClientImpl import dev.slne.surf.cloud.core.common.netty.network.protocol.login.ServerboundLoginStartPacket import dev.slne.surf.cloud.standalone.netty.server.network.ServerRunningPacketListenerImpl +import java.net.InetSocketAddress class ServerClientImpl( val server: NettyServerImpl, serverId: Long, serverCategory: String, - serverName: String -) : - CommonNettyClientImpl(serverId, serverCategory, serverName) { + serverName: String, + val lobbyServer: Boolean, +) : CommonNettyClientImpl(serverId, serverCategory, serverName) { + + override lateinit var playAddress: InetSocketAddress private var _listener: ServerRunningPacketListenerImpl? = null - set(value) { - field = value - if (value != null) { - initConnection(value.connection) - } - } val listener get() = _listener ?: error("listener not yet set") + override val velocitySecret: ByteArray + get() = ProxySecretHolder.currentSecret() + fun initListener(listener: ServerRunningPacketListenerImpl) { _listener = listener } @@ -32,6 +32,12 @@ class ServerClientImpl( companion object { fun fromPacket(server: NettyServerImpl, packet: ServerboundLoginStartPacket) = - ServerClientImpl(server, packet.serverId, packet.serverCategory, packet.serverName) + ServerClientImpl( + server, + packet.serverId, + packet.serverCategory, + packet.serverName, + packet.lobby + ) } } \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/connection/ServerConnectionListener.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/connection/ServerConnectionListener.kt index f68db096..3dd7cdc8 100644 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/connection/ServerConnectionListener.kt +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/connection/ServerConnectionListener.kt @@ -2,22 +2,16 @@ package dev.slne.surf.cloud.standalone.netty.server.connection import dev.slne.surf.cloud.api.common.netty.network.protocol.PacketFlow import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket +import dev.slne.surf.cloud.api.common.netty.packet.RespondingNettyPacket import dev.slne.surf.cloud.api.common.util.DefaultUncaughtExceptionHandlerWithName -import dev.slne.surf.cloud.api.common.util.mutableObjectListOf import dev.slne.surf.cloud.api.common.util.netty.suspend -import dev.slne.surf.cloud.api.common.util.synchronize import dev.slne.surf.cloud.api.common.util.threadFactory import dev.slne.surf.cloud.core.common.config.cloudConfig import dev.slne.surf.cloud.core.common.coroutines.ConnectionManagementScope -import dev.slne.surf.cloud.core.common.netty.network.ConnectionImpl -import dev.slne.surf.cloud.core.common.netty.network.DisconnectReason -import dev.slne.surf.cloud.core.common.netty.network.DisconnectionDetails -import dev.slne.surf.cloud.core.common.netty.network.EncryptionManager -import dev.slne.surf.cloud.core.common.netty.network.HandlerNames +import dev.slne.surf.cloud.core.common.netty.network.* import dev.slne.surf.cloud.core.common.netty.network.protocol.common.ClientboundBundlePacket import dev.slne.surf.cloud.core.common.netty.network.protocol.running.ClientboundDisconnectPacket import dev.slne.surf.cloud.standalone.netty.server.NettyServerImpl -import dev.slne.surf.cloud.standalone.netty.server.network.ServerEncryptionManager import dev.slne.surf.cloud.standalone.netty.server.network.ServerHandshakePacketListenerImpl import dev.slne.surf.surfapi.core.api.util.logger import io.netty.bootstrap.ServerBootstrap @@ -33,14 +27,12 @@ import io.netty.handler.flush.FlushConsolidationHandler import io.netty.handler.timeout.ReadTimeoutHandler import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext -import org.jetbrains.annotations.Blocking import java.net.InetAddress import java.net.InetSocketAddress import java.net.SocketAddress import java.util.concurrent.ConcurrentLinkedQueue +import java.util.concurrent.CopyOnWriteArrayList class ServerConnectionListener(val server: NettyServerImpl) { @@ -50,87 +42,78 @@ class ServerConnectionListener(val server: NettyServerImpl) { var running = false private set - private val channels = mutableObjectListOf().synchronize() - private val channelsMutex = Mutex() - - val connections = mutableObjectListOf().synchronize() - val connectionsMutex = Mutex() - + private val channels = CopyOnWriteArrayList() + val connections = CopyOnWriteArrayList() private val pending = ConcurrentLinkedQueue() init { running = true } - suspend fun startTcpServerListener(address: InetAddress?, port: Int) { bind(InetSocketAddress(address, port)) } suspend fun bind(address: SocketAddress) = withContext(Dispatchers.IO) { - channelsMutex.withLock { - val channelClass: Class - val eventloopgroup: EventLoopGroup - - if (Epoll.isAvailable() && cloudConfig.connectionConfig.nettyConfig.useEpoll) { - channelClass = - if (address is DomainSocketAddress) EpollServerDomainSocketChannel::class.java else EpollServerSocketChannel::class.java - eventloopgroup = SERVER_EPOLL_EVENT_GROUP - log.atInfo().log("Using epoll channel type") - } else { - channelClass = NioServerSocketChannel::class.java - eventloopgroup = SERVER_EVENT_GROUP - log.atInfo().log("Using default channel type") - } + val channelClass: Class + val eventloopgroup: EventLoopGroup + + if (Epoll.isAvailable() && cloudConfig.connectionConfig.nettyConfig.useEpoll) { + channelClass = + if (address is DomainSocketAddress) EpollServerDomainSocketChannel::class.java else EpollServerSocketChannel::class.java + eventloopgroup = SERVER_EPOLL_EVENT_GROUP + log.atInfo().log("Using epoll channel type") + } else { + channelClass = NioServerSocketChannel::class.java + eventloopgroup = SERVER_EVENT_GROUP + log.atInfo().log("Using default channel type") + } - channels.add( - ServerBootstrap() - .channel(channelClass) - .group(eventloopgroup) - .localAddress(address) - .option(ChannelOption.AUTO_READ, false) - .childHandler(object : ChannelInitializer() { - override fun initChannel(channel: Channel) { - runCatching { - channel.config().setOption(ChannelOption.TCP_NODELAY, true) - } - - val pipeline = channel.pipeline() - .addFirst(FlushConsolidationHandler()) - .addLast(HandlerNames.TIMEOUT, ReadTimeoutHandler(30)) - - ConnectionImpl.configureSerialization( - pipeline, - PacketFlow.SERVERBOUND, - false - ) - - val connection = - ConnectionImpl(PacketFlow.SERVERBOUND, EncryptionManager.instance) - - pending.add(connection) - connection.configurePacketHandler(channel, pipeline) - - val loggableAddress = connection.getLoggableAddress(logIps) - log.atInfo() - .log("Configured packet handler for $loggableAddress") - - connection.setListenerForServerboundHandshake( - ServerHandshakePacketListenerImpl(server, connection) - ) + channels.add( + ServerBootstrap() + .channel(channelClass) + .group(eventloopgroup) + .localAddress(address) + .option(ChannelOption.AUTO_READ, false) + .childHandler(object : ChannelInitializer() { + override fun initChannel(channel: Channel) { + runCatching { + channel.config().setOption(ChannelOption.TCP_NODELAY, true) } - }) - .bind() - .suspend() - ) - } + + val pipeline = channel.pipeline() + .addFirst(FlushConsolidationHandler()) + .addLast(HandlerNames.TIMEOUT, ReadTimeoutHandler(30)) + + ConnectionImpl.configureSerialization( + pipeline, + PacketFlow.SERVERBOUND, + false + ) + + val connection = + ConnectionImpl(PacketFlow.SERVERBOUND, EncryptionManager.instance) + + pending.add(connection) + connection.configurePacketHandler(channel, pipeline) + + val loggableAddress = connection.getLoggableAddress(logIps) + log.atInfo() + .log("Configured packet handler for $loggableAddress") + + connection.setListenerForServerboundHandshake( + ServerHandshakePacketListenerImpl(server, connection) + ) + } + }) + .bind() + .suspend() + ) } - suspend fun acceptConnections() { - channelsMutex.withLock { - for (future in channels) { - future.channel().config().isAutoRead = true - } + fun acceptConnections() { + for (future in channels) { + future.channel().config().isAutoRead = true } } @@ -141,10 +124,10 @@ class ServerConnectionListener(val server: NettyServerImpl) { try { val details = DisconnectionDetails(DisconnectReason.SERVER_SHUTDOWN) connection.sendWithIndication(ClientboundDisconnectPacket(details)) - connection.disconnect(details) + connection.disconnect(details)?.suspend() } catch (_: Throwable) { try { - connection.channel.close().sync() + connection.channel.close().suspend() } catch (e: InterruptedException) { log.atSevere().withCause(e).log("Interrupted whilst closing channel") } catch (e: Exception) { @@ -154,10 +137,8 @@ class ServerConnectionListener(val server: NettyServerImpl) { } - connectionsMutex.withLock { - for (connection in connections) { - disconnect(connection) - } + for (connection in connections) { + disconnect(connection) } connections.clear() @@ -166,66 +147,65 @@ class ServerConnectionListener(val server: NettyServerImpl) { } pending.clear() - channelsMutex.withLock { - for (future in channels) { - try { - future.channel().close().sync() - } catch (e: InterruptedException) { - log.atSevere().withCause(e).log("Interrupted whilst closing channel") - } + for (future in channels) { + try { + future.channel().close().suspend() + } catch (e: InterruptedException) { + log.atSevere().withCause(e).log("Interrupted whilst closing channel") } } + + ConnectionImpl.NETWORK_EPOLL_WORKER_GROUP.shutdownGracefully().suspend() + ConnectionImpl.NETWORK_WORKER_GROUP.shutdownGracefully().suspend() } private suspend fun addPending() { - var connection: ConnectionImpl - while ((pending.poll().also { connection = it }) != null) { - connectionsMutex.withLock { - connections.add(connection) - connection.isPending = false - } + while (true) { + val connection = pending.poll() ?: break + connections.add(connection) + connection.isPending = false } } suspend fun tick() { addPending() - connectionsMutex.withLock { - val iterator = connections.iterator() - for (connection in iterator) { - if (connection.connecting) { - continue - } - - if (connection.connected) { - try { - connection.tick() - } catch (e: Exception) { - log.atWarning() - .withCause(e) - .log("Failed to handle packet for ${connection.getLoggableAddress(logIps)}") - - ConnectionManagementScope.launch { - val details = DisconnectionDetails(DisconnectReason.INTERNAL_EXCEPTION, e.message) - connection.sendWithIndication(ClientboundDisconnectPacket(details)) - connection.disconnect(details) - } + for (connection in connections) { + if (connection.connecting) { + continue + } - connection.setReadOnly() - } - } else { - if (connection.preparing) { - continue + if (connection.connected) { + try { + connection.tick() + } catch (e: Exception) { + log.atWarning() + .withCause(e) + .log("Failed to handle packet for ${connection.getLoggableAddress(logIps)}") + + ConnectionManagementScope.launch { + val details = + DisconnectionDetails(DisconnectReason.INTERNAL_EXCEPTION, e.message) + connection.sendWithIndication(ClientboundDisconnectPacket(details)) + connection.disconnect(details) } - iterator.remove() - connection.handleDisconnection() + connection.setReadOnly() + } + } else { + if (connection.preparing) { + continue } + + connections.remove(connection) + connection.handleDisconnection() } } } fun broadcast(packet: NettyPacket, flush: Boolean = true) { + require(packet !is RespondingNettyPacket<*>) { "Cannot broadcast responding packets" } + val activeProtocols = packet.protocols for (connection in connections) { if (connection.outboundProtocolInfo.id !in activeProtocols) continue @@ -235,6 +215,8 @@ class ServerConnectionListener(val server: NettyServerImpl) { fun broadcast(packets: List, flush: Boolean = true) { if (packets.isEmpty()) return + require(packets.none { it is RespondingNettyPacket<*> }) { "Cannot broadcast responding packets" } + val packet = if (packets.size == 1) packets.first() else ClientboundBundlePacket(packets) val activeProtocols = packet.protocols diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/ServerCommonPacketListenerImpl.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/ServerCommonPacketListenerImpl.kt index e1f2624d..c26387be 100644 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/ServerCommonPacketListenerImpl.kt +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/ServerCommonPacketListenerImpl.kt @@ -50,7 +50,7 @@ abstract class ServerCommonPacketListenerImpl( var suspendFlushingOnServerThread = false private set - val latency get() = keepAlive.latency + val latency get() = connection.latency var keepConnectionAlive = true @OverridingMethodsMustInvokeSuper diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/ServerLoginPacketListenerImpl.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/ServerLoginPacketListenerImpl.kt index bc6a3d1f..d7b4237b 100644 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/ServerLoginPacketListenerImpl.kt +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/ServerLoginPacketListenerImpl.kt @@ -6,9 +6,7 @@ import dev.slne.surf.cloud.core.common.netty.network.DisconnectReason import dev.slne.surf.cloud.core.common.netty.network.DisconnectionDetails import dev.slne.surf.cloud.core.common.netty.network.protocol.login.* import dev.slne.surf.cloud.core.common.netty.network.protocol.prerunning.PreRunningProtocols -import dev.slne.surf.cloud.standalone.config.standaloneConfig import dev.slne.surf.cloud.standalone.netty.server.NettyServerImpl -import dev.slne.surf.cloud.standalone.netty.server.ProxyServerAutoregistration import dev.slne.surf.cloud.standalone.netty.server.ServerClientImpl import dev.slne.surf.surfapi.core.api.util.logger @@ -52,14 +50,12 @@ class ServerLoginPacketListenerImpl(val server: NettyServerImpl, val connection: private fun startClientVerification() { state = State.VERIFYING - if (proxy && standaloneConfig.useSingleProxySetup && ProxyServerAutoregistration.hasProxy) { - disconnect(DisconnectReason.PROXY_ALREADY_CONNECTED) - } +// if (proxy && standaloneConfig.useSingleProxySetup && ProxyServerAutoregistration.hasProxy) { +// disconnect(DisconnectReason.PROXY_ALREADY_CONNECTED) +// } } private fun verifyLoginAndFinishConnectionSetup() { - // compression would go here - finishLoginAndWaitForClient() } @@ -71,6 +67,7 @@ class ServerLoginPacketListenerImpl(val server: NettyServerImpl, val connection: override suspend fun handleLoginAcknowledgement(packet: ServerboundLoginAcknowledgedPacket) { check(state == State.PROTOCOL_SWITCHING) { "Unexpected login acknowledgement packet" } + client!!.initConnection(connection) connection.setupOutboundProtocol(PreRunningProtocols.CLIENTBOUND) val listener = ServerPreRunningPacketListenerImpl(server, connection, client!!, proxy) connection.setupInboundProtocol(PreRunningProtocols.SERVERBOUND, listener) diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/ServerPreRunningPacketListenerImpl.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/ServerPreRunningPacketListenerImpl.kt index db3ce6f5..05f7702f 100644 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/ServerPreRunningPacketListenerImpl.kt +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/ServerPreRunningPacketListenerImpl.kt @@ -2,7 +2,7 @@ package dev.slne.surf.cloud.standalone.netty.server.network import dev.slne.surf.cloud.core.common.netty.network.ConnectionImpl import dev.slne.surf.cloud.core.common.netty.network.protocol.prerunning.* -import dev.slne.surf.cloud.core.common.netty.network.protocol.running.RunningProtocols +import dev.slne.surf.cloud.core.common.netty.network.protocol.synchronizing.SynchronizingProtocols import dev.slne.surf.cloud.standalone.netty.server.NettyServerImpl import dev.slne.surf.cloud.standalone.netty.server.ServerClientImpl @@ -36,20 +36,21 @@ class ServerPreRunningPacketListenerImpl( override fun handleRequestContinuation(packet: ServerboundRequestContinuation) { check(state == State.PRE_RUNNING_ACKNOWLEDGED) { "Cannot proceed to running state from $state" } - send(ClientboundReadyToRunPacket) + send(ClientboundProceedToSynchronizingPacket) } - override suspend fun handleReadyToRun(packet: ServerboundReadyToRunPacket) { + override suspend fun handleReadyToRun(packet: ServerboundProceedToSynchronizingAcknowledgedPacket) { check(state == State.PRE_RUNNING_ACKNOWLEDGED) { "Cannot proceed to running state from $state" } - connection.setupOutboundProtocol(RunningProtocols.CLIENTBOUND) - val listener = ServerRunningPacketListenerImpl(server, client, connection) + connection.setupOutboundProtocol(SynchronizingProtocols.CLIENTBOUND) + val listener = ServerSynchronizingPacketListenerImpl(server, connection, client, proxy) connection.setupInboundProtocol( - RunningProtocols.SERVERBOUND, + SynchronizingProtocols.SERVERBOUND, listener ) - client.initListener(listener) - server.registerClient(client, proxy) + + client.playAddress = packet.playAddress + listener.startSynchronizing() } override fun isAcceptingMessages(): Boolean { diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/ServerRunningPacketListenerImpl.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/ServerRunningPacketListenerImpl.kt index 2082fc2f..980b9972 100644 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/ServerRunningPacketListenerImpl.kt +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/ServerRunningPacketListenerImpl.kt @@ -1,5 +1,6 @@ package dev.slne.surf.cloud.standalone.netty.server.network +import dev.slne.surf.cloud.api.common.netty.network.ConnectionProtocol import dev.slne.surf.cloud.api.common.netty.network.protocol.respond import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket import dev.slne.surf.cloud.api.common.netty.packet.NettyPacketInfo @@ -25,6 +26,7 @@ import dev.slne.surf.cloud.standalone.player.StandaloneCloudPlayerImpl import dev.slne.surf.cloud.standalone.server.StandaloneCloudServerImpl import dev.slne.surf.cloud.standalone.server.StandaloneProxyCloudServerImpl import dev.slne.surf.cloud.standalone.server.serverManagerImpl +import dev.slne.surf.cloud.standalone.sync.SyncRegistryImpl import dev.slne.surf.surfapi.core.api.util.logger import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -50,12 +52,19 @@ class ServerRunningPacketListenerImpl( packet.name, packet.proxy, packet.playerIp, - packet.serverUid + packet.serverUid, + true ) packet.respond(PlayerConnectToServerResponsePacket(result)) - broadcast(packet.copy()) + broadcast(PlayerConnectedToServerPacket( + packet.uuid, + packet.name, + packet.serverUid, + packet.proxy, + packet.playerIp + )) serverManagerImpl.getCommonStandaloneServerByUid(packet.serverUid) ?.handlePlayerConnect(packet.uuid) } @@ -488,11 +497,31 @@ class ServerRunningPacketListenerImpl( } } + override fun handleSyncValueChange(packet: SyncValueChangePacket) { + try { + SyncRegistryImpl.instance.handleChangePacket(packet, connection) + } catch (e: Throwable) { + log.atWarning() + .withCause(e) + .log("Failed to handle sync value change packet: %s", packet.syncId) + } + } + + override fun handleSyncSetDelta(packet: SyncSetDeltaPacket) { + try { + SyncRegistryImpl.instance.handleSyncSetDeltaPacket(packet, connection) + } catch (e: Throwable) { + log.atWarning() + .withCause(e) + .log("Failed to handle sync set delta packet: %s", packet.setId) + } + } + override fun handlePacket(packet: NettyPacket) { val listeners = NettyListenerRegistry.getListeners(packet.javaClass) ?: return if (listeners.isEmpty()) return - val info = NettyPacketInfo(connection) + val info = NettyPacketInfo(connection, ConnectionProtocol.RUNNING) for (listener in listeners) { PacketHandlerScope.launch { diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/ServerSynchronizingPacketListenerImpl.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/ServerSynchronizingPacketListenerImpl.kt new file mode 100644 index 00000000..654b028d --- /dev/null +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/ServerSynchronizingPacketListenerImpl.kt @@ -0,0 +1,142 @@ +package dev.slne.surf.cloud.standalone.netty.server.network + +import dev.slne.surf.cloud.api.common.netty.network.ConnectionProtocol +import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket +import dev.slne.surf.cloud.api.common.netty.packet.NettyPacketInfo +import dev.slne.surf.cloud.core.common.coroutines.BeforeStartTaskScope +import dev.slne.surf.cloud.core.common.coroutines.PacketHandlerScope +import dev.slne.surf.cloud.core.common.netty.network.ConnectionImpl +import dev.slne.surf.cloud.core.common.netty.network.protocol.common.ClientboundSetVelocitySecretPacket +import dev.slne.surf.cloud.core.common.netty.network.protocol.running.ClientboundBatchUpdateServer +import dev.slne.surf.cloud.core.common.netty.network.protocol.running.RunningProtocols +import dev.slne.surf.cloud.core.common.netty.network.protocol.running.SyncSetDeltaPacket +import dev.slne.surf.cloud.core.common.netty.network.protocol.running.SyncValueChangePacket +import dev.slne.surf.cloud.core.common.netty.network.protocol.synchronizing.ClientboundSynchronizeFinishPacket +import dev.slne.surf.cloud.core.common.netty.network.protocol.synchronizing.FinishSynchronizingPacket +import dev.slne.surf.cloud.core.common.netty.network.protocol.synchronizing.ServerSynchronizingPacketListener +import dev.slne.surf.cloud.core.common.netty.network.protocol.synchronizing.ServerboundSynchronizeFinishAcknowledgedPacket +import dev.slne.surf.cloud.core.common.netty.registry.listener.NettyListenerRegistry +import dev.slne.surf.cloud.core.common.plugin.task.CloudSynchronizeTaskManager +import dev.slne.surf.cloud.standalone.netty.server.NettyServerImpl +import dev.slne.surf.cloud.standalone.netty.server.ProxySecretHolder +import dev.slne.surf.cloud.standalone.netty.server.ServerClientImpl +import dev.slne.surf.cloud.standalone.netty.server.network.config.SynchronizeRegistriesTask +import dev.slne.surf.cloud.standalone.server.serverManagerImpl +import dev.slne.surf.cloud.standalone.sync.SyncRegistryImpl +import dev.slne.surf.surfapi.core.api.util.logger +import kotlinx.coroutines.launch + +class ServerSynchronizingPacketListenerImpl( + server: NettyServerImpl, + connection: ConnectionImpl, + client: ServerClientImpl, + val proxy: Boolean +) : ServerCommonPacketListenerImpl(server, client, connection), ServerSynchronizingPacketListener { + + private val log = logger() + + @Volatile + var state: State = State.START + private set + + @Volatile + private var clientFinishedSynchronizing = false + + override suspend fun tick0() { + super.tick0() + if (clientFinishedSynchronizing && state == State.WAIT_FOR_CLIENT) { + finalize() + } + } + + fun startSynchronizing() { + check(state == State.START) { "Cannot start synchronizing from state $state" } + state = State.SYNCHRONIZING + + BeforeStartTaskScope.launch { + send(ClientboundSetVelocitySecretPacket(ProxySecretHolder.currentSecret())) + send(ClientboundBatchUpdateServer(serverManagerImpl.retrieveAllServers())) + + SynchronizeRegistriesTask.execute(client) + CloudSynchronizeTaskManager.executeTasks(client) + + state = State.WAIT_FOR_CLIENT + } + } + + private fun finalize() { + state = State.FINALIZING + connection.send(ClientboundSynchronizeFinishPacket) + } + + override suspend fun handleFinishSynchronizing(packet: FinishSynchronizingPacket) { + clientFinishedSynchronizing = true + } + + override suspend fun handleSynchronizeFinishAcknowledged(packet: ServerboundSynchronizeFinishAcknowledgedPacket) { + check(state == State.FINALIZING) { "Unexpected synchronize finish acknowledgement packet" } + + connection.setupOutboundProtocol(RunningProtocols.CLIENTBOUND) + val listener = ServerRunningPacketListenerImpl(server, client, connection) + connection.setupInboundProtocol(RunningProtocols.SERVERBOUND, listener) + client.initListener(listener) + server.registerClient(client, proxy) + state = State.SYNCHRONIZED + } + + override fun handleSyncValueChange(packet: SyncValueChangePacket) { + try { + SyncRegistryImpl.instance.updateSyncValue(packet.syncId, packet.value) + } catch (e: Exception) { + log.atWarning() + .withCause(e) + .log("Failed to update sync value for packet $packet") + } + } + + override fun handleSyncSetDelta(packet: SyncSetDeltaPacket) { + try { + SyncRegistryImpl.instance.handleSyncSetDeltaPacket(packet, connection) + } catch (e: Exception) { + log.atWarning() + .withCause(e) + .log("Failed to handle sync set delta for packet $packet") + } + } + + override fun handlePacket(packet: NettyPacket) { + val listeners = NettyListenerRegistry.getListeners(packet.javaClass) ?: return + if (listeners.isEmpty()) return + + val info = NettyPacketInfo(connection, ConnectionProtocol.SYNCHRONIZING) + + for (listener in listeners) { + PacketHandlerScope.launch { + try { + listener.handle(packet, info) + } catch (e: Throwable) { + log.atWarning() + .withCause(e) + .log( + "Failed to call listener %s for packet %s", + listener::class.simpleName, + packet::class.simpleName + ) + } + } + } + } + + + override fun isAcceptingMessages(): Boolean { + return connection.connected + } + + enum class State { + START, + SYNCHRONIZING, + WAIT_FOR_CLIENT, + FINALIZING, + SYNCHRONIZED, + } +} \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/config/FinishSynchronizeTask.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/config/FinishSynchronizeTask.kt new file mode 100644 index 00000000..1f9b374a --- /dev/null +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/config/FinishSynchronizeTask.kt @@ -0,0 +1,11 @@ +package dev.slne.surf.cloud.standalone.netty.server.network.config + +import dev.slne.surf.cloud.api.common.netty.NettyClient +import dev.slne.surf.cloud.api.common.plugin.spring.task.CloudInitialSynchronizeTask +import dev.slne.surf.cloud.core.common.netty.network.protocol.synchronizing.FinishSynchronizingPacket + +object FinishSynchronizeTask : CloudInitialSynchronizeTask { + override suspend fun execute(client: NettyClient) { + client.connection.send(FinishSynchronizingPacket) + } +} \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/config/SynchronizeRegistriesTask.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/config/SynchronizeRegistriesTask.kt new file mode 100644 index 00000000..d4e725ab --- /dev/null +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/netty/server/network/config/SynchronizeRegistriesTask.kt @@ -0,0 +1,15 @@ +package dev.slne.surf.cloud.standalone.netty.server.network.config + +import dev.slne.surf.cloud.api.common.netty.NettyClient +import dev.slne.surf.cloud.api.common.plugin.spring.task.CloudInitialSynchronizeTask +import dev.slne.surf.cloud.core.common.netty.network.protocol.synchronizing.ClientboundBatchSyncSetPacket +import dev.slne.surf.cloud.core.common.netty.network.protocol.synchronizing.ClientboundBatchSyncValuePacket +import dev.slne.surf.cloud.standalone.sync.SyncRegistryImpl + +object SynchronizeRegistriesTask : CloudInitialSynchronizeTask { + + override suspend fun execute(client: NettyClient) { + client.connection.send(ClientboundBatchSyncValuePacket(SyncRegistryImpl.instance.prepareBatchSyncValues())) + client.connection.send(ClientboundBatchSyncSetPacket(SyncRegistryImpl.instance.prepareBatchSyncSets())) + } +} \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/persistent/PlayerDataStorage.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/persistent/PlayerDataStorage.kt index 8090efa8..60a4cd73 100644 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/persistent/PlayerDataStorage.kt +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/persistent/PlayerDataStorage.kt @@ -4,10 +4,11 @@ import dev.slne.surf.cloud.api.common.util.nbt.FastNbtIo import dev.slne.surf.cloud.api.common.util.safeReplaceFile import dev.slne.surf.cloud.standalone.player.StandaloneCloudPlayerImpl import dev.slne.surf.surfapi.core.api.util.logger -import net.querz.nbt.tag.CompoundTag +import net.kyori.adventure.nbt.CompoundBinaryTag import java.nio.file.Files import java.nio.file.StandardCopyOption import java.time.LocalDateTime +import kotlin.io.path.createTempFile object PlayerDataStorage { @@ -17,11 +18,11 @@ object PlayerDataStorage { val playerDir = DataStorage.storageDir.resolve("playerdata").also { it.mkdirs() } fun save(player: StandaloneCloudPlayerImpl) = runCatching { - val tag = CompoundTag() - player.savePlayerData(tag) - val tempDataFile = Files.createTempFile(playerDir.toPath(), player.uuid.toString() + "-", ".dat") + val tag = CompoundBinaryTag.builder() - FastNbtIo.writeCompressed(tag, tempDataFile) + player.savePlayerData(tag) + val tempDataFile = createTempFile(playerDir.toPath(), player.uuid.toString() + "-", ".dat") + FastNbtIo.writeCompressed(tag.build(), tempDataFile) val playerFile = playerDir.resolve("${player.uuid}.dat").toPath() val oldPlayerFile = playerDir.resolve("${player.uuid}.dat_old").toPath() @@ -31,15 +32,13 @@ object PlayerDataStorage { log.atWarning().withCause(it).log("Failed to save player data for player ${player.uuid}") } - fun load(player: StandaloneCloudPlayerImpl): CompoundTag? { + fun load(player: StandaloneCloudPlayerImpl): CompoundBinaryTag? { val tag = loadFile(player.uuid.toString()) ?: return null - player.readPlayerData(tag) - return tag } - private fun loadFile(uuid: String): CompoundTag? { + private fun loadFile(uuid: String): CompoundBinaryTag? { val loaded = loadFile(uuid, "dat") if (loaded == null) { @@ -49,11 +48,11 @@ object PlayerDataStorage { return loaded ?: loadFile(uuid, "dat_old") } - private fun loadFile(fileName: String, extension: String): CompoundTag? { + private fun loadFile(fileName: String, extension: String): CompoundBinaryTag? { val playerFile = playerDir.resolve("$fileName.$extension") if (!playerFile.exists() || !playerFile.isFile) return null - return FastNbtIo.readCompressed(playerFile.toPath(), DataStorage.MAX_STACK_DEPTH) + return FastNbtIo.readCompressed(playerFile.toPath()) } private fun backup(fileName: String, extension: String) { diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/persistent/StandalonePersistentData.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/persistent/StandalonePersistentData.kt index bf45cfaa..0335aa34 100644 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/persistent/StandalonePersistentData.kt +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/persistent/StandalonePersistentData.kt @@ -1,9 +1,16 @@ package dev.slne.surf.cloud.standalone.persistent import dev.slne.surf.cloud.core.common.data.persistentData -import net.querz.nbt.tag.LongTag +import net.kyori.adventure.nbt.BinaryTagTypes +import net.kyori.adventure.nbt.LongBinaryTag object StandalonePersistentData { val SERVER_ID_COUNTER = - persistentData("server_id_counter", { LongTag(it) }, { asLong() }, 1L).nonNull() + persistentData( + "server_id_counter", + BinaryTagTypes.LONG, + { value() }, + { LongBinaryTag.longBinaryTag(it) }, + 1L + ).nonNull() } \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/StandaloneCloudPlayerImpl.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/StandaloneCloudPlayerImpl.kt index de0daed0..a33d51a0 100644 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/StandaloneCloudPlayerImpl.kt +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/StandaloneCloudPlayerImpl.kt @@ -36,6 +36,7 @@ import net.kyori.adventure.audience.MessageType import net.kyori.adventure.bossbar.BossBar import net.kyori.adventure.identity.Identity import net.kyori.adventure.inventory.Book +import net.kyori.adventure.nbt.CompoundBinaryTag import net.kyori.adventure.resource.ResourcePackCallback import net.kyori.adventure.resource.ResourcePackRequest import net.kyori.adventure.sound.Sound @@ -45,7 +46,6 @@ import net.kyori.adventure.text.Component import net.kyori.adventure.text.ComponentLike import net.kyori.adventure.title.Title import net.kyori.adventure.title.TitlePart -import net.querz.nbt.tag.CompoundTag import java.net.Inet4Address import java.time.ZonedDateTime import java.time.temporal.ChronoUnit @@ -72,8 +72,8 @@ class StandaloneCloudPlayerImpl(uuid: UUID, name: String, val ip: Inet4Address) override val connectedToProxy get() = proxyServer != null override val connectedToServer get() = server != null - val anyServer: ServerCommonCloudServer - get() = server ?: proxyServer ?: error("Player is not connected to a server") + val anyServer: ServerCommonCloudServer? + get() = server ?: proxyServer @Deprecated("remove") @Volatile @@ -93,17 +93,15 @@ class StandaloneCloudPlayerImpl(uuid: UUID, name: String, val ip: Inet4Address) var sessionStartTime: ZonedDateTime = ZonedDateTime.now() - fun savePlayerData(tag: CompoundTag) { + fun savePlayerData(tag: CompoundBinaryTag.Builder) { if (!ppdc.empty) { tag.put("ppdc", ppdc.toTagCompound()) } } - fun readPlayerData(tag: CompoundTag) { - val ppdcTag = tag.get("ppdc") - if (ppdcTag is CompoundTag) { - ppdc.fromTagCompound(ppdcTag) - } + fun readPlayerData(tag: CompoundBinaryTag) { + val ppdcTag = tag.get("ppdc") as? CompoundBinaryTag ?: return + ppdc.fromTagCompound(ppdcTag) } override fun isAfk(): Boolean { @@ -147,7 +145,7 @@ class StandaloneCloudPlayerImpl(uuid: UUID, name: String, val ip: Inet4Address) } suspend fun getPersistentData() = ppdcMutex.withLock { ppdc.toTagCompound() } - suspend fun updatePersistentData(tag: CompoundTag) = + suspend fun updatePersistentData(tag: CompoundBinaryTag) = ppdcMutex.withLock { ppdc.fromTagCompound(tag) } override suspend fun latestIpAddress(): Inet4Address { @@ -181,7 +179,11 @@ class StandaloneCloudPlayerImpl(uuid: UUID, name: String, val ip: Inet4Address) } override suspend fun lastServerRaw(): String { - return anyServer.name + return anyServer?.name ?: error("Player is not connected to a server") + } + + override fun currentServer(): CloudServer { + return server ?: error("Player is not connected to a server") } override suspend fun firstSeen(): ZonedDateTime? { @@ -195,7 +197,9 @@ class StandaloneCloudPlayerImpl(uuid: UUID, name: String, val ip: Inet4Address) } override suspend fun displayName(): Component = ClientboundRequestDisplayNamePacket(uuid) - .fireAndAwaitUrgent(anyServer.connection)?.displayName + .fireAndAwaitUrgent( + anyServer?.connection ?: error("Player is not connected to a server") + )?.displayName ?: error("Failed to get display name (probably timed out)") override suspend fun name(): String { @@ -310,7 +314,11 @@ class StandaloneCloudPlayerImpl(uuid: UUID, name: String, val ip: Inet4Address) } override suspend fun getLuckpermsMetaData(key: String): String? { - return RequestLuckpermsMetaDataPacket(uuid, key).fireAndAwait(anyServer.connection)?.data + return anyServer?.connection?.let { + RequestLuckpermsMetaDataPacket(uuid, key).fireAndAwait( + it + )?.data + } } @@ -404,7 +412,9 @@ class StandaloneCloudPlayerImpl(uuid: UUID, name: String, val ip: Inet4Address) } override suspend fun hasPermission(permission: String): Boolean { - return RequestPlayerPermissionPacket(uuid, permission).awaitOrThrow(anyServer.connection) + return RequestPlayerPermissionPacket(uuid, permission).awaitOrThrow( + anyServer?.connection ?: error("Player is not connected to a server") + ) } override fun playSound(sound: Sound) { @@ -472,6 +482,6 @@ class StandaloneCloudPlayerImpl(uuid: UUID, name: String, val ip: Inet4Address) } private fun send(packet: NettyPacket) { - anyServer.connection.send(packet) + anyServer?.connection?.send(packet) } } \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/StandaloneCloudPlayerManagerImpl.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/StandaloneCloudPlayerManagerImpl.kt index b8d2fe92..cbeda0db 100644 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/StandaloneCloudPlayerManagerImpl.kt +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/StandaloneCloudPlayerManagerImpl.kt @@ -3,6 +3,7 @@ package dev.slne.surf.cloud.standalone.player import com.google.auto.service.AutoService import dev.slne.surf.cloud.api.common.player.CloudPlayerManager import dev.slne.surf.cloud.api.common.player.OfflineCloudPlayer +import dev.slne.surf.cloud.api.common.player.task.PrePlayerJoinTask import dev.slne.surf.cloud.api.server.export.PlayerDataExport import dev.slne.surf.cloud.api.server.export.PlayerDataExportEmpty import dev.slne.surf.cloud.core.common.coroutines.PlayerDataSaveScope @@ -10,7 +11,6 @@ import dev.slne.surf.cloud.core.common.messages.MessageManager import dev.slne.surf.cloud.core.common.netty.network.protocol.running.ClientboundRunPrePlayerJoinTasksPacket import dev.slne.surf.cloud.core.common.player.CloudPlayerManagerImpl import dev.slne.surf.cloud.core.common.player.playerManagerImpl -import dev.slne.surf.cloud.core.common.player.task.PrePlayerJoinTask import dev.slne.surf.cloud.core.common.player.task.PrePlayerJoinTaskManager import dev.slne.surf.cloud.core.common.util.bean import dev.slne.surf.cloud.core.common.util.checkInstantiationByServiceLoader @@ -48,7 +48,7 @@ class StandaloneCloudPlayerManagerImpl : CloudPlayerManagerImpl().runTasks(player) + val serverResult = PrePlayerJoinTaskManager.runTasks(player) if (serverResult !is PrePlayerJoinTask.Result.ALLOWED) return serverResult val connections = serverManagerImpl.retrieveAllServers().map { it.connection } diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/db/exposed/CloudPlayerService.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/db/exposed/CloudPlayerService.kt index e0d819ed..87832227 100644 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/db/exposed/CloudPlayerService.kt +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/db/exposed/CloudPlayerService.kt @@ -3,6 +3,7 @@ package dev.slne.surf.cloud.standalone.player.db.exposed import dev.slne.surf.cloud.api.common.player.name.NameHistoryFactory import dev.slne.surf.cloud.api.common.util.mutableObjectListOf import dev.slne.surf.cloud.api.server.exposed.service.AbstractExposedDAOService +import dev.slne.surf.cloud.api.server.plugin.CoroutineTransactional import dev.slne.surf.cloud.core.common.player.playtime.PlaytimeEntry import dev.slne.surf.cloud.standalone.player.StandaloneCloudPlayerImpl import dev.slne.surf.cloud.standalone.player.name.create @@ -12,6 +13,7 @@ import java.time.ZonedDateTime import java.util.* @Service +@CoroutineTransactional class CloudPlayerService : AbstractExposedDAOService({ maximumSize(1000) }) { @@ -47,8 +49,7 @@ class CloudPlayerService : AbstractExposedDAOService({ update(player.uuid, createIfMissing = true) { lastSeen = ZonedDateTime.now() lastIpAddress = player.ip - lastServer = - player.server?.name ?: error("Player ${player.uuid} is not connected to a server") + player.server?.let { lastServer = it.name } val latestName = nameHistories.minByOrNull { it.createdAt } val currentName = player.name() @@ -75,13 +76,11 @@ class CloudPlayerService : AbstractExposedDAOService({ } suspend fun updatePlaytimeInSession(uuid: UUID, playtimeId: Long, playtimeSeconds: Long) = - withTransaction { - CloudPlayerPlaytimesEntity.findByIdAndUpdate(playtimeId) { - it.durationSeconds = playtimeSeconds - } + CloudPlayerPlaytimesEntity.findByIdAndUpdate(playtimeId) { + it.durationSeconds = playtimeSeconds } - suspend fun loadPlaytimeEntries(uuid: UUID) = withTransaction { + suspend fun loadPlaytimeEntries(uuid: UUID) = find(uuid)?.playtimes?.mapTo(mutableObjectListOf()) { PlaytimeEntry( id = it.id.value, @@ -91,5 +90,4 @@ class CloudPlayerService : AbstractExposedDAOService({ createdAt = it.createdAt, ) } ?: mutableObjectListOf() - } } \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/db/exposed/punishment/LtwTestAspect.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/db/exposed/punishment/LtwTestAspect.kt new file mode 100644 index 00000000..bb4f0a66 --- /dev/null +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/db/exposed/punishment/LtwTestAspect.kt @@ -0,0 +1,2 @@ +package dev.slne.surf.cloud.standalone.player.db.exposed.punishment + diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/punishment/PunishmentManagerImpl.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/punishment/PunishmentManagerImpl.kt index ebf242c1..b2c22ceb 100644 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/punishment/PunishmentManagerImpl.kt +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/punishment/PunishmentManagerImpl.kt @@ -4,33 +4,27 @@ import com.github.benmanes.caffeine.cache.Caffeine import com.sksamuel.aedile.core.expireAfterWrite import dev.slne.surf.cloud.api.common.event.offlineplayer.punishment.CloudPlayerPunishEvent import dev.slne.surf.cloud.api.common.event.offlineplayer.punishment.CloudPlayerPunishmentUpdatedEvent +import dev.slne.surf.cloud.api.common.player.OfflineCloudPlayer import dev.slne.surf.cloud.api.common.player.punishment.type.PunishmentAttachedIpAddress.PunishmentAttachedIpAddressImpl import dev.slne.surf.cloud.api.common.player.punishment.type.note.PunishmentNote.PunishmentNoteImpl +import dev.slne.surf.cloud.api.common.player.task.PrePlayerJoinTask import dev.slne.surf.cloud.api.common.util.toObjectList import dev.slne.surf.cloud.api.server.exposed.table.AuditableLongEntityClass import dev.slne.surf.cloud.api.server.netty.packet.broadcast -import dev.slne.surf.cloud.core.common.coroutines.PunishmentDatabaseScope import dev.slne.surf.cloud.core.common.coroutines.PunishmentHandlerScope import dev.slne.surf.cloud.core.common.netty.network.protocol.running.ClientboundTriggerPunishmentCreatedEventPacket import dev.slne.surf.cloud.core.common.netty.network.protocol.running.ClientboundTriggerPunishmentUpdateEventPacket -import dev.slne.surf.cloud.core.common.player.CommonOfflineCloudPlayerImpl import dev.slne.surf.cloud.core.common.player.PunishmentCacheImpl import dev.slne.surf.cloud.core.common.player.PunishmentManager import dev.slne.surf.cloud.core.common.player.punishment.type.* -import dev.slne.surf.cloud.core.common.player.task.PrePlayerJoinTask import dev.slne.surf.cloud.standalone.player.db.exposed.punishment.entity.* import dev.slne.surf.cloud.standalone.player.db.exposed.punishment.table.* import dev.slne.surf.surfapi.core.api.util.logger import dev.slne.surf.surfapi.core.api.util.random +import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch -import org.jetbrains.exposed.dao.LongEntity import org.jetbrains.exposed.dao.LongEntityClass -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.SqlExpressionBuilder.greater -import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction -import org.jetbrains.exposed.sql.transactions.experimental.suspendedTransactionAsync import org.springframework.core.annotation.Order import org.springframework.stereotype.Component import java.time.ZonedDateTime @@ -40,7 +34,8 @@ import dev.slne.surf.cloud.api.common.event.offlineplayer.punishment.CloudPlayer @Component @Order(PrePlayerJoinTask.PUNISHMENT_MANAGER) -class PunishmentManagerImpl : PunishmentManager, PrePlayerJoinTask { +class PunishmentManagerImpl(private val service: PunishmentService) : PunishmentManager, + PrePlayerJoinTask { private val log = logger() @@ -54,42 +49,23 @@ class PunishmentManagerImpl : PunishmentManager, PrePlayerJoinTask { private const val PUNISHMENT_ID_LENGTH = 6 } - override suspend fun generatePunishmentId(): String { + override suspend fun generatePunishmentId(): String = coroutineScope { val id = buildString(PUNISHMENT_ID_LENGTH) { repeat(PUNISHMENT_ID_LENGTH) { append(PUNISHMENT_ID_CHARS[random.nextInt(PUNISHMENT_ID_CHARS.length)]) } } - val banCount = withTransactionAsync { - BanPunishmentTable.selectAll() - .where { BanPunishmentTable.punishmentId eq id } - .count() - } - - val kickCount = withTransactionAsync { - KickPunishmentTable.selectAll() - .where { KickPunishmentTable.punishmentId eq id } - .count() - } - - val muteCount = withTransactionAsync { - MutePunishmentTable.selectAll() - .where { MutePunishmentTable.punishmentId eq id } - .count() - } - - val warningCount = withTransactionAsync { - WarnPunishmentTable.selectAll() - .where { WarnPunishmentTable.punishmentId eq id } - .count() - } + val banCount = async { service.countBansByPunishmentId(id) } + val kickCount = async { service.countKicksByPunishmentId(id) } + val muteCount = async { service.countMutesByPunishmentId(id) } + val warningCount = async { service.countWarningsByPunishmentId(id) } if (banCount.await() + kickCount.await() + muteCount.await() + warningCount.await() > 0) { - return generatePunishmentId() + generatePunishmentId() + } else { + id } - - return id } override suspend fun createKick( @@ -97,24 +73,19 @@ class PunishmentManagerImpl : PunishmentManager, PrePlayerJoinTask { issuerUuid: UUID?, reason: String?, initialNotes: List - ): PunishmentKickImpl = withTransaction { + ): PunishmentKickImpl { val punishmentId = generatePunishmentId() - val punishment = KickPunishmentEntity.new { - this.punishmentId = punishmentId - this.punishedUuid = punishedUuid - this.issuerUuid = issuerUuid - this.reason = reason - } - - createNotes(initialNotes, punishment) { p, note -> - KickPunishmentNoteEntity.new { - this.punishment = p - this.note = note - } - } + val kick = service.createKickWithNotes( + punishmentId, + punishedUuid, + issuerUuid, + reason, + initialNotes + ) - punishment.toApiObject() - }.also { broadcastPunishmentCreation(it) } + broadcastPunishmentCreation(kick) + return kick + } override suspend fun createWarn( @@ -122,24 +93,19 @@ class PunishmentManagerImpl : PunishmentManager, PrePlayerJoinTask { issuerUuid: UUID?, reason: String?, initialNotes: List - ): PunishmentWarnImpl = withTransaction { + ): PunishmentWarnImpl { val punishmentId = generatePunishmentId() - val punishment = WarnPunishmentEntity.new { - this.punishmentId = punishmentId - this.punishedUuid = punishedUuid - this.issuerUuid = issuerUuid - this.reason = reason - } - - createNotes(initialNotes, punishment) { p, note -> - WarnPunishmentNoteEntity.new { - this.punishment = p - this.note = note - } - } + val warning = service.createWarningWithNotes( + punishmentId, + punishedUuid, + issuerUuid, + reason, + initialNotes + ) - punishment.toApiObject() - }.also { broadcastPunishmentCreation(it) } + broadcastPunishmentCreation(warning) + return warning + } override suspend fun createMute( @@ -149,26 +115,21 @@ class PunishmentManagerImpl : PunishmentManager, PrePlayerJoinTask { permanent: Boolean, expirationDate: ZonedDateTime?, initialNotes: List - ): PunishmentMuteImpl = withTransaction { + ): PunishmentMuteImpl { val punishmentId = generatePunishmentId() - val punishment = MutePunishmentEntity.new { - this.punishmentId = punishmentId - this.punishedUuid = punishedUuid - this.issuerUuid = issuerUuid - this.reason = reason - this.expirationDate = expirationDate - this.permanent = permanent - } - - createNotes(initialNotes, punishment) { p, note -> - MutePunishmentNoteEntity.new { - this.punishment = p - this.note = note - } - } + val punishment = service.createMuteWithNotes( + punishmentId, + punishedUuid, + issuerUuid, + reason, + permanent, + expirationDate, + initialNotes + ) - punishment.toApiObject() - }.also { broadcastPunishmentCreation(it) } + broadcastPunishmentCreation(punishment) + return punishment + } override suspend fun createBan( punishedUuid: UUID, @@ -180,35 +141,24 @@ class PunishmentManagerImpl : PunishmentManager, PrePlayerJoinTask { raw: Boolean, initialNotes: List, initialIpAddresses: List - ): PunishmentBanImpl = withTransaction { + ): PunishmentBanImpl { val punishmentId = generatePunishmentId() - val punishment = BanPunishmentEntity.new { - this.punishmentId = punishmentId - this.punishedUuid = punishedUuid - this.issuerUuid = issuerUuid - this.reason = reason - this.expirationDate = expirationDate - this.permanent = permanent - this.securityBan = securityBan - this.raw = raw - } - - createNotes(initialNotes, punishment) { p, note -> - BanPunishmentNoteEntity.new { - this.punishment = p - this.note = note - } - } - - for (ip in initialIpAddresses) { - BanPunishmentIpAddressEntity.new { - this.punishment = punishment - this.ipAddress = ip - } - } + val punishment = service.createBanWithNotesAndIpAddresses( + punishmentId, + punishedUuid, + issuerUuid, + reason, + permanent, + expirationDate, + securityBan, + raw, + initialNotes, + initialIpAddresses + ) - punishment.toApiObject() - }.also { broadcastPunishmentCreation(it) } + broadcastPunishmentCreation(punishment) + return punishment + } override suspend fun broadcastBan(ban: PunishmentBanImpl) { broadcastPunishmentCreation(ban) @@ -242,42 +192,9 @@ class PunishmentManagerImpl : PunishmentManager, PrePlayerJoinTask { broadcastPunishmentUpdate(warn, UpdateOperation.ADMIN_PANEL) } - private fun createNotes( - notes: List, - punishment: T, - factory: (T, String) -> Unit - ) { - for (note in notes) { - factory(punishment, note) - } - } - override suspend fun attachIpAddressToBan(id: Long, rawIp: String): Boolean { - val (exists, ban, ip) = withTransaction { - val exists = BanPunishmentIpAddressTable - .select(BanPunishmentIpAddressTable.id) - .where { - (BanPunishmentIpAddressTable.punishment eq id) and (BanPunishmentIpAddressTable.ipAddress eq rawIp) - } - .limit(1) - .any() - - if (exists) { - return@withTransaction Triple(true, null, null) - } - - val ban = BanPunishmentEntity.findById(id) ?: error("Ban with id $id not found") - val note = BanPunishmentIpAddressEntity.new { - this.punishment = ban - this.ipAddress = rawIp - } - - Triple(false, ban.toApiObject(), note.toApiObject()) - } - - if (exists) { - return false - } + val (exists, ban, ip) = service.attachIpAddressToBan(id, rawIp) + if (exists) return false broadcastPunishmentUpdate(ban!!, UpdateOperation.ATTACH_IP(ip!!)) return true @@ -318,20 +235,17 @@ class PunishmentManagerImpl : PunishmentManager, PrePlayerJoinTask { suspend fun , Api : AbstractPunishment> attachNoteToPunishment( id: Long, entityClass: LongEntityClass, - notEntityClass: LongEntityClass, + noteEntityClass: LongEntityClass, note: String, toApi: (PunishmentEntity) -> Api, ): PunishmentNoteImpl { - val (punishment, note) = withTransaction { - val entity = entityClass.findById(id) ?: error("Punishment with id $id not found") - val note = notEntityClass.new { - this.note = note - this.punishment = entity - } - - toApi(entity) to note.toApiObject() - } - + val (punishment, note) = service.attachNoteToPunishment( + id, + entityClass, + noteEntityClass, + note, + toApi + ) broadcastPunishmentUpdate(punishment, UpdateOperation.NOTE_ADDED(note)) return note } @@ -349,18 +263,12 @@ class PunishmentManagerImpl : PunishmentManager, PrePlayerJoinTask { fetchNotesForPunishment(id, WarnPunishmentNoteEntity) override suspend fun fetchIpAddressesForBan(id: Long): List = - withTransaction { - BanPunishmentIpAddressEntity.find { BanPunishmentIpAddressTable.punishment eq id } - .map { it.toApiObject() } - } + service.fetchIpAddressesForBan(id) private suspend fun

> fetchNotesForPunishment( id: Long, noteEntityClass: LongEntityClass - ) = withTransaction { - noteEntityClass.find { BanPunishmentNoteTable.punishment eq id } - .map { it.toApiObject() } - } + ) = service.fetchNotesForPunishment(id, noteEntityClass) override suspend fun fetchMutes( punishedUuid: UUID, @@ -385,25 +293,7 @@ class PunishmentManagerImpl : PunishmentManager, PrePlayerJoinTask { override suspend fun fetchIpBans( ip: String, onlyActive: Boolean - ): List = withTransaction { - val banIds = BanPunishmentIpAddressTable.select(BanPunishmentIpAddressTable.punishment) - .where { BanPunishmentIpAddressTable.ipAddress eq ip } - .map { it[BanPunishmentIpAddressTable.punishment] } - .toSet() - - if (banIds.isEmpty()) { - return@withTransaction emptyList() - } - - val query = if (onlyActive) { - BanPunishmentEntity.find { activePunishmentFilter(BanPunishmentTable) and (BanPunishmentTable.id inList banIds) } - } else { - BanPunishmentEntity.find { BanPunishmentTable.id inList banIds } - } - - query.map { it.toApiObject() } - } - + ): List = service.fetchIpBans(ip, onlyActive) override suspend fun fetchWarnings(punishedUuid: UUID): List = fetchPunishments( @@ -425,73 +315,30 @@ class PunishmentManagerImpl : PunishmentManager, PrePlayerJoinTask { punishedUuid: UUID, onlyActive: Boolean, toApiObject: (E) -> T - ): List { - return withTransaction { - val query = if (onlyActive) { - entityClass.find { - punishedUuidFilter(table, punishedUuid) and activePunishmentFilter(table) - } - } else { - entityClass.find { - punishedUuidFilter(table, punishedUuid) - } - } - - query.map { toApiObject(it) } - } - } + ): List = service.fetchPunishments(entityClass, table, punishedUuid, onlyActive, toApiObject) private suspend fun fetchPunishments( entityClass: AuditableLongEntityClass, table: AbstractPunishmentTable, punishedUuid: UUID, toApiObject: (E) -> T - ): List { - return withTransaction { - entityClass.find { punishedUuidFilter(table, punishedUuid) }.map { toApiObject(it) } - } - } - - private fun activePunishmentFilter( - table: T - ): Op { - return (table.unpunished eq false) and - ((table.permanent eq true) or - (table.expirationDate greater ZonedDateTime.now())) - } - - private fun punishedUuidFilter( - table: T, - punishedUuid: UUID - ): Op { - return (table.punishedUuid eq punishedUuid) - } + ): List = service.fetchPunishments(entityClass, table, punishedUuid, toApiObject) override suspend fun getCurrentLoginValidationPunishmentCache(playerUuid: UUID): PunishmentCacheImpl? { return preJoinPunishmentCache.getIfPresent(playerUuid) } - override suspend fun preJoin(player: CommonOfflineCloudPlayerImpl): PrePlayerJoinTask.Result { + override suspend fun preJoin(player: OfflineCloudPlayer): PrePlayerJoinTask.Result { val uuid = player.uuid preJoinPunishmentCache.put(uuid, fetchPunishmentCache(uuid)) return PrePlayerJoinTask.Result.ALLOWED } private suspend fun fetchPunishmentCache(uuid: UUID): PunishmentCacheImpl = coroutineScope { - val mutesDeferred = withTransactionAsync { - fetchPunishments(MutePunishmentEntity, MutePunishmentTable, uuid) { it.toApiObject() } - } - val bansDeferred = withTransactionAsync { - fetchPunishments(BanPunishmentEntity, BanPunishmentTable, uuid) { it.toApiObject() } - } - - val kicksDeferred = withTransactionAsync { - fetchPunishments(KickPunishmentEntity, KickPunishmentTable, uuid) { it.toApiObject() } - } - - val warningsDeferred = withTransactionAsync { - fetchPunishments(WarnPunishmentEntity, WarnPunishmentTable, uuid) { it.toApiObject() } - } + val mutesDeferred = async { fetchMutes(uuid, onlyActive = false) } + val bansDeferred = async { fetchBans(uuid, onlyActive = false) } + val kicksDeferred = async { fetchKicks(uuid) } + val warningsDeferred = async { fetchWarnings(uuid) } PunishmentCacheImpl( mutes = mutesDeferred.await().sorted().toObjectList(), @@ -545,10 +392,4 @@ class PunishmentManagerImpl : PunishmentManager, PrePlayerJoinTask { .log("Failed to broadcast punishment create event for punishment $createdPunishment") } } - - private suspend fun withTransaction(statement: suspend Transaction.() -> T) = - newSuspendedTransaction(PunishmentDatabaseScope.context, statement = statement) - - private suspend fun withTransactionAsync(statement: suspend Transaction.() -> T) = - suspendedTransactionAsync(PunishmentDatabaseScope.context, statement = statement) } \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/punishment/PunishmentService.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/punishment/PunishmentService.kt new file mode 100644 index 00000000..799cd97a --- /dev/null +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/punishment/PunishmentService.kt @@ -0,0 +1,525 @@ +package dev.slne.surf.cloud.standalone.player.punishment + +import dev.slne.surf.cloud.api.common.player.punishment.type.PunishmentAttachedIpAddress.PunishmentAttachedIpAddressImpl +import dev.slne.surf.cloud.api.common.player.punishment.type.note.PunishmentNote.PunishmentNoteImpl +import dev.slne.surf.cloud.api.server.exposed.table.AuditableLongEntityClass +import dev.slne.surf.cloud.api.server.plugin.CoroutineTransactional +import dev.slne.surf.cloud.core.common.player.punishment.type.* +import dev.slne.surf.cloud.standalone.player.db.exposed.punishment.entity.* +import dev.slne.surf.cloud.standalone.player.db.exposed.punishment.table.* +import dev.slne.surf.surfapi.core.api.util.logger +import org.jetbrains.exposed.dao.LongEntity +import org.jetbrains.exposed.dao.LongEntityClass +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.SqlExpressionBuilder.greater +import org.springframework.stereotype.Service +import java.io.Serial +import java.time.ZonedDateTime +import java.util.* + +/** + * Exception thrown when a punishment or related entity is not found. + */ +class PunishmentNotFoundException(entity: String, id: Long) : + NoSuchElementException("$entity punishment with id $id not found") { + companion object { + @Serial + private const val serialVersionUID: Long = -8983773040936961978L + } +} + +/** + * Service for managing player punishments, including creation, querying, and note/IP operations. + */ +@Service +@CoroutineTransactional +class PunishmentService { + private val log = logger() + + // region -- Count operations -- + + /** + * Counts the number of ban punishments with the given punishment ID. + * + * @param punishmentId unique identifier of the punishment + * @return count of matching ban records + */ + suspend fun countBansByPunishmentId(punishmentId: String): Long { + require(punishmentId.isNotBlank()) { "punishmentId must not be blank" } + log.atFine().log("Counting bans for punishmentId=%s", punishmentId) + return BanPunishmentTable.selectAll() + .where { BanPunishmentTable.punishmentId eq punishmentId } + .count() + } + + /** + * Counts the number of kick punishments with the given punishment ID. + * + * @param punishmentId unique identifier of the punishment + * @return count of matching kick records + */ + suspend fun countKicksByPunishmentId(punishmentId: String): Long { + require(punishmentId.isNotBlank()) { "punishmentId must not be blank" } + log.atFine().log("Counting kicks for punishmentId=%s", punishmentId) + return KickPunishmentTable.selectAll() + .where { KickPunishmentTable.punishmentId eq punishmentId } + .count() + } + + /** + * Counts the number of mute punishments with the given punishment ID. + * + * @param punishmentId unique identifier of the punishment + * @return count of matching mute records + */ + suspend fun countMutesByPunishmentId(punishmentId: String): Long { + require(punishmentId.isNotBlank()) { "punishmentId must not be blank" } + log.atFine().log("Counting mutes for punishmentId=%s", punishmentId) + return MutePunishmentTable.selectAll() + .where { MutePunishmentTable.punishmentId eq punishmentId } + .count() + } + + /** + * Counts the number of warning punishments with the given punishment ID. + * + * @param punishmentId unique identifier of the punishment + * @return count of matching warning records + */ + suspend fun countWarningsByPunishmentId(punishmentId: String): Long { + require(punishmentId.isNotBlank()) { "punishmentId must not be blank" } + log.atFine().log("Counting warnings for punishmentId=%s", punishmentId) + return WarnPunishmentTable.selectAll() + .where { WarnPunishmentTable.punishmentId eq punishmentId } + .count() + } + + // endregion + // region -- Creation with initial notes and IPs -- + + /** + * Creates a kick punishment with associated notes. + * + * @param punishmentId unique identifier for the punishment + * @param punishedUuid UUID of the punished player + * @param issuerUuid UUID of the issuer (nullable) + * @param reason reason for the punishment (nullable) + * @param initialNotes list of initial note texts + * @return API representation of the created kick punishment + */ + suspend fun createKickWithNotes( + punishmentId: String, + punishedUuid: UUID, + issuerUuid: UUID?, + reason: String?, + initialNotes: List + ): PunishmentKickImpl { + require(punishmentId.isNotBlank()) { "punishmentId must not be blank" } + log.atInfo().log( + "Creating Kick punishment id=%s punishedUuid=%s issuerUuid=%s", + punishmentId, + punishedUuid, + issuerUuid + ) + + val punishment = KickPunishmentEntity.new { + this.punishmentId = punishmentId + this.punishedUuid = punishedUuid + this.issuerUuid = issuerUuid + this.reason = reason + } + + if (initialNotes.isNotEmpty()) { + KickPunishmentNoteTable.batchInsert(initialNotes) { noteText -> + this[KickPunishmentNoteTable.punishment] = punishment.id + this[KickPunishmentNoteTable.note] = noteText + } + } + + return punishment.toApiObject() + } + + + /** + * Creates a warning punishment with associated notes. + * + * @param punishmentId unique identifier for the punishment + * @param punishedUuid UUID of the punished player + * @param issuerUuid UUID of the issuer (nullable) + * @param reason reason for the punishment (nullable) + * @param initialNotes list of initial note texts + * @return API representation of the created warning punishment + */ + suspend fun createWarningWithNotes( + punishmentId: String, + punishedUuid: UUID, + issuerUuid: UUID?, + reason: String?, + initialNotes: List + ): PunishmentWarnImpl { + require(punishmentId.isNotBlank()) { "punishmentId must not be blank" } + log.atInfo().log( + "Creating Warn punishment id=%s punishedUuid=%s issuerUuid=%s", + punishmentId, + punishedUuid, + issuerUuid + ) + + val punishment = WarnPunishmentEntity.new { + this.punishmentId = punishmentId + this.punishedUuid = punishedUuid + this.issuerUuid = issuerUuid + this.reason = reason + } + + if (initialNotes.isNotEmpty()) { + WarnPunishmentNoteTable.batchInsert(initialNotes) { noteText -> + this[WarnPunishmentNoteTable.punishment] = punishment.id + this[WarnPunishmentNoteTable.note] = noteText + } + } + + return punishment.toApiObject() + } + + /** + * Creates a mute punishment with associated notes. + * + * @param punishmentId unique identifier for the punishment + * @param punishedUuid UUID of the punished player + * @param issuerUuid UUID of the issuer (nullable) + * @param reason reason for the punishment (nullable) + * @param permanent indicates if the punishment is permanent + * @param expirationDate date when the punishment expires (nullable) + * @param initialNotes list of initial note texts + * @return API representation of the created mute punishment + */ + suspend fun createMuteWithNotes( + punishmentId: String, + punishedUuid: UUID, + issuerUuid: UUID?, + reason: String?, + permanent: Boolean, + expirationDate: ZonedDateTime?, + initialNotes: List + ): PunishmentMuteImpl { + require(punishmentId.isNotBlank()) { "punishmentId must not be blank" } + log.atInfo().log( + "Creating Mute punishment id=%s punishedUuid=%s issuerUuid=%s permanent=%s", + punishmentId, + punishedUuid, + issuerUuid, + permanent + ) + + val punishment = MutePunishmentEntity.new { + this.punishmentId = punishmentId + this.punishedUuid = punishedUuid + this.issuerUuid = issuerUuid + this.reason = reason + this.expirationDate = expirationDate + this.permanent = permanent + } + + if (initialNotes.isNotEmpty()) { + MutePunishmentNoteTable.batchInsert(initialNotes) { noteText -> + this[MutePunishmentNoteTable.punishment] = punishment.id + this[MutePunishmentNoteTable.note] = noteText + } + } + + return punishment.toApiObject() + } + + /** + * Creates a ban punishment with associated notes and IP addresses. + * + * @param punishmentId unique identifier for the punishment + * @param punishedUuid UUID of the punished player + * @param issuerUuid UUID of the issuer (nullable) + * @param reason reason for the punishment (nullable) + * @param permanent indicates if the punishment is permanent + * @param expirationDate date when the punishment expires (nullable) + * @param securityBan flag for security ban + * @param raw raw flag + * @param initialNotes list of initial note texts + * @param initialIpAddresses list of IP addresses to attach + * @return API representation of the created ban punishment + */ + suspend fun createBanWithNotesAndIpAddresses( + punishmentId: String, + punishedUuid: UUID, + issuerUuid: UUID?, + reason: String?, + permanent: Boolean, + expirationDate: ZonedDateTime?, + securityBan: Boolean, + raw: Boolean, + initialNotes: List, + initialIpAddresses: List + ): PunishmentBanImpl { + require(punishmentId.isNotBlank()) { "punishmentId must not be blank" } + log.atInfo().log( + "Creating Ban punishment id=%s punishedUuid=%s issuerUuid=%s permanent=%s securityBan=%s raw=%s", + punishmentId, + punishedUuid, + issuerUuid, + permanent, + securityBan, + raw + ) + + val punishment = BanPunishmentEntity.new { + this.punishmentId = punishmentId + this.punishedUuid = punishedUuid + this.issuerUuid = issuerUuid + this.reason = reason + this.expirationDate = expirationDate + this.permanent = permanent + this.securityBan = securityBan + this.raw = raw + } + + if (initialNotes.isNotEmpty()) { + BanPunishmentNoteTable.batchInsert(initialNotes) { noteText -> + this[BanPunishmentNoteTable.punishment] = punishment.id + this[BanPunishmentNoteTable.note] = noteText + } + } + if (initialIpAddresses.isNotEmpty()) { + BanPunishmentIpAddressTable.batchInsert(initialIpAddresses) { ipText -> + this[BanPunishmentIpAddressTable.punishment] = punishment.id + this[BanPunishmentIpAddressTable.ipAddress] = ipText + } + } + + return punishment.toApiObject() + } + + // endregion + // region -- Attachment operations -- + + /** + * Attaches an IP address to an existing ban if not already present. + * + * @param id database ID of the ban punishment + * @param rawIp IP address string to attach + * @return Triple of (alreadyExists, banApiObject?, attachedIpApiObject?) + */ + suspend fun attachIpAddressToBan( + id: Long, + rawIp: String + ): Triple { + require(rawIp.isNotBlank()) { "IP address must not be blank" } + log.atFine().log("Attaching IP %s to ban id=%s", rawIp, id) + + val exists = BanPunishmentIpAddressTable + .select(BanPunishmentIpAddressTable.id) + .where { + (BanPunishmentIpAddressTable.punishment eq id) and (BanPunishmentIpAddressTable.ipAddress eq rawIp) + } + .limit(1) + .any() + + if (exists) { + log.atFine().log("IP %s already attached to ban id=%s", rawIp, id) + return Triple(true, null, null) + } + + val ban = BanPunishmentEntity.findById(id) ?: throw PunishmentNotFoundException("Ban", id) + val ip = BanPunishmentIpAddressEntity.new { + this.punishment = ban + this.ipAddress = rawIp + } + log.atInfo().log("Attached IP %s to ban id=%s, entryId=%s", rawIp, id, ip.id.value) + + return Triple(false, ban.toApiObject(), ip.toApiObject()) + } + + /** + * Attaches a note to a punishment entity. + * + * @param id database ID of the punishment + * @param entityClass DAO class of the punishment + * @param noteEntityClass DAO class of the note entity + * @param note text of the note to attach + * @param toApi function mapping entity to API object + * @return Pair of (punishmentApiObject, createdNoteApiObject) + */ + suspend fun , Api : AbstractPunishment> attachNoteToPunishment( + id: Long, + entityClass: LongEntityClass, + noteEntityClass: LongEntityClass, + note: String, + toApi: (PunishmentEntity) -> Api, + ): Pair { + require(note.isNotBlank()) { "Note must not be blank" } + log.atFine().log("Attaching note to punishment id=%s note=%s", id, note) + + val entity = entityClass.findById(id) ?: throw PunishmentNotFoundException("Punishment", id) + val note = noteEntityClass.new { + this.note = note + this.punishment = entity + } + log.atInfo().log("Attached note id=%s to punishment id=%s", note.id.value, id) + + return toApi(entity) to note.toApiObject() + } + + // endregion + // region -- Fetch operations -- + + /** + * Retrieves all IP addresses attached to a ban. + * + * @param id database ID of the ban + * @return list of attached IP address API objects + */ + suspend fun fetchIpAddressesForBan(id: Long): List { + log.atFine().log("Fetching IP addresses for ban id=%s", id) + return BanPunishmentIpAddressEntity.find { BanPunishmentIpAddressTable.punishment eq id } + .map { it.toApiObject() } + } + + /** + * Retrieves all notes for a given punishment. + * + * @param id database ID of the punishment + * @param noteEntityClass DAO class of the note entity + * @return list of note API objects + */ + suspend fun

> fetchNotesForPunishment( + id: Long, + noteEntityClass: LongEntityClass + ): List { + log.atFine().log("Fetching notes for punishment id=%s", id) + return noteEntityClass.find { BanPunishmentNoteTable.punishment eq id } + .map { it.toApiObject() } + } + + /** + * Finds ban punishments by IP address, optionally filtering active bans. + * + * @param ip IP address string to search + * @param onlyActive whether to include only active bans + * @return list of matching ban punishment API objects + */ + suspend fun fetchIpBans( + ip: String, + onlyActive: Boolean + ): List { + require(ip.isNotBlank()) { "IP address must not be blank" } + log.atFine().log("Fetching ip bans for ip=%s onlyActive=%s", ip, onlyActive) + val banIds = BanPunishmentIpAddressTable.select(BanPunishmentIpAddressTable.punishment) + .where { BanPunishmentIpAddressTable.ipAddress eq ip } + .map { it[BanPunishmentIpAddressTable.punishment] } + .toSet() + + if (banIds.isEmpty()) return emptyList() + + val now = ZonedDateTime.now() + + val query = if (onlyActive) { + BanPunishmentEntity.find { + activePunishmentFilter( + BanPunishmentTable, + now + ) and (BanPunishmentTable.id inList banIds) + } + } else { + BanPunishmentEntity.find { BanPunishmentTable.id inList banIds } + } + log.atFine().log("Found %s bans for ip=%s", query.count(), ip) + + return query.map { it.toApiObject() } + } + + /** + * Fetches punishments for a given player UUID, with optional filtering for only active expirable punishments. + * + * @param entityClass DAO class of the punishment entity + * @param table corresponding table for filtering + * @param punishedUuid UUID of the punished player + * @param onlyActive whether to include only active punishments + * @param toApiObject mapping from entity to API object + * @return list of punishment API objects + */ + suspend fun fetchPunishments( + entityClass: AuditableLongEntityClass, + table: AbstractUnpunishableExpirablePunishmentTable, + punishedUuid: UUID, + onlyActive: Boolean, + toApiObject: (E) -> T + ): List { + log.atFine().log("Fetching expirable punishments for uuid=%s onlyActive=%s", punishedUuid, onlyActive) + + val now = ZonedDateTime.now() + val query = if (onlyActive) { + entityClass.find { + punishedUuidFilter(table, punishedUuid) and activePunishmentFilter(table, now) + } + } else { + entityClass.find { + punishedUuidFilter(table, punishedUuid) + } + } + + return query.map { toApiObject(it) } + } + + /** + * Fetches punishments for a given player UUID without expiration filtering. + * + * @param entityClass DAO class of the punishment entity + * @param table corresponding table for filtering + * @param punishedUuid UUID of the punished player + * @param toApiObject mapping from entity to API object + * @return list of punishment API objects + */ + suspend fun fetchPunishments( + entityClass: AuditableLongEntityClass, + table: AbstractPunishmentTable, + punishedUuid: UUID, + toApiObject: (E) -> T + ): List { + log.atFine().log("Fetching all punishments for uuid=%s", punishedUuid) + return entityClass.find { punishedUuidFilter(table, punishedUuid) }.map { toApiObject(it) } + } + + // endregion + // region -- Internal helpers -- + + /** + * Creates note entities for a punishment. + */ + private fun createNotes( + notes: List, + punishment: T, + factory: (T, String) -> Unit + ) { + for (note in notes) { + factory(punishment, note) + } + } + + + /** + * Builds a filter for active, unpunished and unexpired punishments. + */ + private fun activePunishmentFilter( + table: T, + now: ZonedDateTime + ): Op = (table.unpunished eq false) and + ((table.permanent eq true) or (table.expirationDate greater now)) + + /** + * Builds a filter for punishments by player UUID. + */ + private fun punishedUuidFilter( + table: T, + punishedUuid: UUID + ): Op = (table.punishedUuid eq punishedUuid) + + // endregion +} \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/punishment/handler/DefaultBanPunishmentHandler.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/punishment/handler/DefaultBanPunishmentHandler.kt index 75f82c32..c9284af3 100644 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/punishment/handler/DefaultBanPunishmentHandler.kt +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/punishment/handler/DefaultBanPunishmentHandler.kt @@ -4,18 +4,17 @@ import com.google.common.flogger.StackSize import dev.slne.surf.cloud.api.common.event.CloudEventHandler import dev.slne.surf.cloud.api.common.event.offlineplayer.punishment.CloudPlayerPunishEvent import dev.slne.surf.cloud.api.common.event.offlineplayer.punishment.CloudPlayerUnpunishEvent +import dev.slne.surf.cloud.api.common.player.OfflineCloudPlayer import dev.slne.surf.cloud.api.common.player.punishment.type.ban.PunishmentBan +import dev.slne.surf.cloud.api.common.player.task.PrePlayerJoinTask import dev.slne.surf.cloud.api.common.util.mapAsync import dev.slne.surf.cloud.core.common.coroutines.PunishmentHandlerScope import dev.slne.surf.cloud.core.common.messages.MessageManager -import dev.slne.surf.cloud.core.common.player.CommonOfflineCloudPlayerImpl import dev.slne.surf.cloud.core.common.player.PunishmentManager -import dev.slne.surf.cloud.core.common.player.task.PrePlayerJoinTask import dev.slne.surf.cloud.standalone.player.StandaloneCloudPlayerImpl import dev.slne.surf.surfapi.core.api.util.logger import kotlinx.coroutines.awaitAll import kotlinx.coroutines.launch -import org.springframework.context.event.EventListener import org.springframework.core.annotation.Order import org.springframework.stereotype.Component @@ -50,7 +49,7 @@ class DefaultBanPunishmentHandler(private val punishmentManager: PunishmentManag } } - override suspend fun preJoin(player: CommonOfflineCloudPlayerImpl): PrePlayerJoinTask.Result { + override suspend fun preJoin(player: OfflineCloudPlayer): PrePlayerJoinTask.Result { val cache = punishmentManager.getCurrentLoginValidationPunishmentCache(player.uuid) if (cache == null) { log.atWarning() diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/plugin/spring/config/PluginLoadTimeWeavingConfiguration.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/plugin/spring/config/PluginLoadTimeWeavingConfiguration.kt index 7ca8e5f9..4d0b7e5b 100644 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/plugin/spring/config/PluginLoadTimeWeavingConfiguration.kt +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/plugin/spring/config/PluginLoadTimeWeavingConfiguration.kt @@ -1,10 +1,9 @@ package dev.slne.surf.cloud.standalone.plugin.spring.config import org.springframework.context.annotation.Configuration -import org.springframework.context.annotation.EnableAspectJAutoProxy import org.springframework.context.annotation.Profile @Configuration(proxyBeanMethods = false) -@EnableAspectJAutoProxy(proxyTargetClass = true) +//@EnableAspectJAutoProxy(proxyTargetClass = true) @Profile("plugin") class PluginLoadTimeWeavingConfiguration \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/server/StandaloneCloudServerImpl.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/server/StandaloneCloudServerImpl.kt index ae6f4d3b..9d016c65 100644 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/server/StandaloneCloudServerImpl.kt +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/server/StandaloneCloudServerImpl.kt @@ -18,6 +18,7 @@ import dev.slne.surf.cloud.standalone.server.queue.repo.QueueRepository import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import java.net.InetSocketAddress import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @@ -25,8 +26,10 @@ class StandaloneCloudServerImpl( uid: Long, group: String, name: String, + playAddress: InetSocketAddress, + lobby: Boolean, override val connection: ConnectionImpl, -) : AbstractCloudServer(uid, group, name), ServerCloudServer, +) : AbstractCloudServer(uid, group, name, playAddress, lobby), ServerCloudServer, CommonStandaloneServer by CommonStandaloneServerImpl() { init { diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/server/StandaloneCloudServerManagerImpl.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/server/StandaloneCloudServerManagerImpl.kt index 72065936..e0bc0d55 100644 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/server/StandaloneCloudServerManagerImpl.kt +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/server/StandaloneCloudServerManagerImpl.kt @@ -5,6 +5,7 @@ import dev.slne.surf.cloud.api.common.netty.network.protocol.awaitOrThrowUrgent import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket import dev.slne.surf.cloud.api.common.player.CloudPlayer import dev.slne.surf.cloud.api.common.player.ConnectionResultEnum +import dev.slne.surf.cloud.api.common.server.CloudServer import dev.slne.surf.cloud.api.common.server.CloudServerManager import dev.slne.surf.cloud.api.common.util.emptyObjectList import dev.slne.surf.cloud.api.common.util.freeze @@ -12,7 +13,6 @@ import dev.slne.surf.cloud.api.common.util.mutableObjectListOf import dev.slne.surf.cloud.api.server.server.ServerCloudServerManager import dev.slne.surf.cloud.api.server.server.ServerCommonCloudServer import dev.slne.surf.cloud.api.server.server.ServerProxyCloudServer -import dev.slne.surf.cloud.core.common.netty.network.protocol.running.ClientboundBatchUpdateServer import dev.slne.surf.cloud.core.common.netty.network.protocol.running.ClientboundRegisterServerPacket import dev.slne.surf.cloud.core.common.netty.network.protocol.running.ClientboundUnregisterServerPacket import dev.slne.surf.cloud.core.common.netty.network.protocol.running.RequestOfflineDisplayNamePacket @@ -22,9 +22,9 @@ import dev.slne.surf.cloud.core.common.util.bean import dev.slne.surf.cloud.standalone.config.standaloneConfig import dev.slne.surf.cloud.standalone.event.server.CloudServerRegisteredEvent import dev.slne.surf.cloud.standalone.netty.server.NettyServerImpl -import dev.slne.surf.cloud.standalone.netty.server.ProxyServerAutoregistration import dev.slne.surf.cloud.standalone.player.StandaloneCloudPlayerImpl import dev.slne.surf.surfapi.core.api.util.logger +import it.unimi.dsi.fastutil.objects.ObjectCollection import it.unimi.dsi.fastutil.objects.ObjectList import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch @@ -43,19 +43,22 @@ class StandaloneCloudServerManagerImpl : CommonCloudServerManagerImpl { + return super.retrieveProxies() as ObjectCollection + } + + @Suppress("UNCHECKED_CAST") + override suspend fun retrieveServers(): ObjectCollection { + return super.retrieveServers() as ObjectCollection + } + private suspend fun singleProxyServer() = serversMutex.withLock { servers.values.single { it is StandaloneProxyCloudServerImpl } } } diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/server/StandaloneProxyCloudServerImpl.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/server/StandaloneProxyCloudServerImpl.kt index cb48c0cb..8ab0f827 100644 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/server/StandaloneProxyCloudServerImpl.kt +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/server/StandaloneProxyCloudServerImpl.kt @@ -5,18 +5,17 @@ import dev.slne.surf.cloud.api.server.server.ServerProxyCloudServer import dev.slne.surf.cloud.core.common.netty.network.ConnectionImpl import dev.slne.surf.cloud.core.common.netty.network.protocol.running.ClientboundTriggerShutdownPacket import dev.slne.surf.cloud.core.common.server.AbstractProxyCloudServer +import java.net.InetSocketAddress class StandaloneProxyCloudServerImpl( uid: Long, group: String, name: String, + playAddress: InetSocketAddress, override val connection: ConnectionImpl -) : AbstractProxyCloudServer(uid, group, name), ServerProxyCloudServer, +) : AbstractProxyCloudServer(uid, group, name, playAddress), ServerProxyCloudServer, CommonStandaloneServer by CommonStandaloneServerImpl() { - fun registerClients(vararg clients: StandaloneCloudServerImpl) { - } - override fun shutdown() { connection.send(ClientboundTriggerShutdownPacket) } diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spark/SparkHook.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spark/SparkHook.kt new file mode 100644 index 00000000..b276482c --- /dev/null +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spark/SparkHook.kt @@ -0,0 +1,200 @@ +package dev.slne.surf.cloud.standalone.spark + +import dev.slne.surf.cloud.api.common.util.TimeLogger +import dev.slne.surf.cloud.api.server.plugin.PluginManager +import dev.slne.surf.cloud.core.common.CloudCoreInstance +import dev.slne.surf.cloud.core.common.coroutines.BaseScope +import dev.slne.surf.cloud.core.common.spring.CloudLifecycleAware +import dev.slne.surf.cloud.standalone.InstrumentationProvider +import dev.slne.surf.cloud.standalone.spark.impl.CloudPlatformInfoImpl +import dev.slne.surf.cloud.standalone.spark.provider.* +import dev.slne.surf.surfapi.core.api.messages.Colors +import dev.slne.surf.surfapi.core.api.util.mutableObjectSetOf +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.future.await +import kotlinx.coroutines.launch +import me.lucko.spark.common.SparkPlatform +import me.lucko.spark.common.SparkPlugin +import me.lucko.spark.common.command.CommandResponseHandler +import me.lucko.spark.common.command.sender.CommandSender +import me.lucko.spark.common.monitor.ping.PlayerPingProvider +import me.lucko.spark.common.platform.MetadataProvider +import me.lucko.spark.common.platform.PlatformInfo +import me.lucko.spark.common.platform.serverconfig.ServerConfigProvider +import me.lucko.spark.common.sampler.source.ClassSourceLookup +import me.lucko.spark.common.sampler.source.SourceMetadata +import me.lucko.spark.common.tick.TickHook +import me.lucko.spark.common.tick.TickReporter +import me.lucko.spark.common.util.SparkThreadFactory +import me.lucko.spark.common.util.classfinder.ClassFinder +import me.lucko.spark.common.util.classfinder.FallbackClassFinder +import me.lucko.spark.common.util.classfinder.InstrumentationClassFinder +import me.lucko.spark.lib.adventure.text.format.TextColor +import me.lucko.spark.standalone.StandaloneCommandSender +import org.springframework.stereotype.Component +import java.io.PrintWriter +import java.io.StringWriter +import java.lang.instrument.Instrumentation +import java.nio.file.Path +import java.util.concurrent.CompletableFuture +import java.util.concurrent.Executors +import java.util.logging.Level +import java.util.stream.Stream +import kotlin.io.path.Path + +@Component +class SparkHook : CloudLifecycleAware { + + lateinit var spark: CloudSparkPlugin + + override suspend fun onBootstrap( + data: CloudCoreInstance.BootstrapData, + timeLogger: TimeLogger + ) { + timeLogger.measureStep("Hooking Spark") { + start() + spark.execute(arrayOf("profiler", "start"), StandaloneCommandSender.SYSTEM_OUT).await() + } + } + + override suspend fun onDisable(timeLogger: TimeLogger) { + timeLogger.measureStep("Disabling Spark") { + spark.disable() + } + } + + fun start() { + spark = CloudSparkPlugin( + InstrumentationProvider.getInstrumentation() + ) + } + + class CloudSparkPlugin(private val instrumentation: Instrumentation) : + SparkPlugin { + + val platform = SparkPlatform(this) + private val sender = mutableObjectSetOf() + + override fun getVersion(): String? { + return "1.10.138" + } + + override fun getPluginDirectory(): Path? { + return Path("spark") + } + + override fun getCommandName(): String? { + return "spark" + } + + override fun getCommandSenders(): Stream? { + return sender.stream() + } + + override fun executeAsync(p0: Runnable?) { + SparkScope.launch { + p0?.run() + } + } + + override fun getPlatformInfo(): PlatformInfo? { + return CloudPlatformInfoImpl + } + + override fun log(p0: Level, p1: String) { + log(p0, p1, null) + } + + override fun log(level: Level, msg: String, throwable: Throwable?) { + val responseHandler = createResponseHandler(StandaloneCommandSender.SYSTEM_OUT) + + if (level.intValue() < Level.WARNING.intValue()) { + responseHandler.replyPrefixed(me.lucko.spark.lib.adventure.text.Component.text(msg)) + } else { + responseHandler.replyPrefixed( + me.lucko.spark.lib.adventure.text.Component.text( + msg, + TextColor.color(Colors.ERROR.value()) + ) + ) + if (throwable != null) { + val stringWriter = StringWriter() + throwable.printStackTrace(PrintWriter(stringWriter)) + responseHandler.replyPrefixed( + me.lucko.spark.lib.adventure.text.Component.text( + stringWriter.toString(), + TextColor.color(Colors.WARNING.value()) + ) + ) + } + } + } + + override fun createClassFinder(): ClassFinder? { + return ClassFinder.combining(*arrayOf(InstrumentationClassFinder(instrumentation), FallbackClassFinder.INSTANCE)) + } + + fun createResponseHandler(sender: StandaloneCommandSender) = + CommandResponseHandler(platform, sender) + + + fun execute( + args: Array, + sender: StandaloneCommandSender + ): CompletableFuture { + return this.platform.executeCommand(sender, args) + } + + fun suggest( + args: Array, + sender: StandaloneCommandSender + ): MutableList { + return this.platform.tabCompleteCommand(sender, args) + } + + fun disable() { + this.platform.disable() + } + + override fun createPlayerPingProvider(): PlayerPingProvider? { + return CloudServerPingProvider + } + + override fun createServerConfigProvider(): ServerConfigProvider? { + return CloudServerConfigProvider + } + + override fun createTickHook(): TickHook? { + return CloudTickHook + } + + override fun createTickReporter(): TickReporter? { + return CloudTickReporter + } + + override fun createClassSourceLookup(): ClassSourceLookup? { + return CloudClassSourceLookup + } + + override fun getKnownSources(): Collection { + return PluginManager.instance.getPlugins().map { plugin -> + val meta = plugin.meta + SourceMetadata( + meta.name, + meta.version, + meta.authors.joinToString(", "), + meta.description, + ) + } + } + + override fun createExtraMetadataProvider(): MetadataProvider? { + return CloudMetadataProvider + } + } +} + +object SparkScope : BaseScope( + dispatcher = Executors.newFixedThreadPool(4, SparkThreadFactory()).asCoroutineDispatcher(), + name = "spark" +) \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spark/impl/CloudPlatformInfoImpl.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spark/impl/CloudPlatformInfoImpl.kt new file mode 100644 index 00000000..299fafc9 --- /dev/null +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spark/impl/CloudPlatformInfoImpl.kt @@ -0,0 +1,26 @@ +package dev.slne.surf.cloud.standalone.spark.impl + +import dev.slne.surf.cloud.api.common.version.CloudVersion +import me.lucko.spark.common.platform.PlatformInfo + +object CloudPlatformInfoImpl: PlatformInfo { + override fun getType(): PlatformInfo.Type? { + return PlatformInfo.Type.APPLICATION + } + + override fun getName(): String? { + return "Server" + } + + override fun getBrand(): String? { + return "Surf Cloud" + } + + override fun getVersion(): String? { + return CloudVersion.fullVersion + } + + override fun getMinecraftVersion(): String? { + return CloudVersion.minecraftVersion + } +} \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spark/provider/CloudClassSourceLookup.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spark/provider/CloudClassSourceLookup.kt new file mode 100644 index 00000000..d4d33dc0 --- /dev/null +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spark/provider/CloudClassSourceLookup.kt @@ -0,0 +1,14 @@ +package dev.slne.surf.cloud.standalone.spark.provider + +import dev.slne.surf.cloud.api.server.plugin.provider.classloader.SpringPluginClassloader +import me.lucko.spark.common.sampler.source.ClassSourceLookup + +object CloudClassSourceLookup: ClassSourceLookup.ByClassLoader() { + override fun identify(classloader: ClassLoader): String? { + if (classloader is SpringPluginClassloader) { + return classloader.meta.name + } + + return null + } +} \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spark/provider/CloudMetadataProvider.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spark/provider/CloudMetadataProvider.kt new file mode 100644 index 00000000..f55334fe --- /dev/null +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spark/provider/CloudMetadataProvider.kt @@ -0,0 +1,67 @@ +package dev.slne.surf.cloud.standalone.spark.provider + +import dev.slne.surf.cloud.api.common.player.CloudPlayerManager +import dev.slne.surf.cloud.api.common.server.CommonCloudServer +import dev.slne.surf.cloud.api.server.server.ServerCloudServer +import dev.slne.surf.cloud.api.server.server.ServerProxyCloudServer +import dev.slne.surf.cloud.core.common.data.PersistentDataImpl +import dev.slne.surf.cloud.standalone.server.serverManagerImpl +import me.lucko.spark.common.platform.MetadataProvider +import me.lucko.spark.lib.gson.JsonObject +import me.lucko.spark.lib.gson.JsonParser +import net.kyori.adventure.nbt.TagStringIO + +object CloudMetadataProvider : MetadataProvider { + override fun get() = mapOf( + "onlinePlayers" to collectOnlinePlayers(), + "serverInfos" to collectServerInfos(), + "persistentData" to collectPersistentData() + ) + + fun collectPersistentData() = JsonObject().apply { + PersistentDataImpl.tag.forEach { (key, tag) -> + add( + key, + JsonParser.parseString( + TagStringIO.builder() + .emitHeterogeneousLists(true) + .build() + .asString(tag) + ) + ) + } + } + + fun collectOnlinePlayers() = JsonObject().apply { + CloudPlayerManager.getOnlinePlayers().forEach { player -> + add(player.uuid.toString(), JsonObject().apply { + addProperty("name", player.name) + addProperty("isAfk", player.isAfk()) + add("server", player.currentServer().toJsonObject()) + }) + } + } + + fun collectServerInfos() = JsonObject().apply { + serverManagerImpl.getAllServersUnsafe().forEach { server -> + add(server.name, server.toJsonObject(true)) + } + } + + fun CommonCloudServer.toJsonObject(fullInfo: Boolean = false): JsonObject { + return JsonObject().apply { + addProperty("uid", uid) + addProperty("group", group) + addProperty("name", name) + if (!fullInfo) return@apply + + addProperty("maxPlayerCount", maxPlayerCount) + addProperty("currentPlayerCount", currentPlayerCount) + addProperty("isProxy", this@toJsonObject is ServerProxyCloudServer) + + if (this@toJsonObject is ServerCloudServer) { + addProperty("hasAllowList", allowlist) + } + } + } +} \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spark/provider/CloudServerConfigProvider.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spark/provider/CloudServerConfigProvider.kt new file mode 100644 index 00000000..6e9742d0 --- /dev/null +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spark/provider/CloudServerConfigProvider.kt @@ -0,0 +1,60 @@ +package dev.slne.surf.cloud.standalone.spark.provider + +import me.lucko.spark.common.platform.serverconfig.ConfigParser +import me.lucko.spark.common.platform.serverconfig.ExcludedConfigFilter +import me.lucko.spark.common.platform.serverconfig.ServerConfigProvider +import me.lucko.spark.lib.gson.Gson +import me.lucko.spark.lib.gson.JsonElement +import org.spongepowered.configurate.ConfigurationNode +import org.spongepowered.configurate.yaml.YamlConfigurationLoader +import java.io.BufferedReader +import java.io.IOException +import kotlin.io.path.Path + + +private object YamlConfigParser : ConfigParser { + private val GSON = Gson() + + override fun load(file: String, filter: ExcludedConfigFilter): JsonElement? { + val values = parse(Path(file)) ?: return null + return filter.apply(GSON.toJsonTree(values)) + } + + @Throws(IOException::class) + override fun parse(reader: BufferedReader): Map { + val config = YamlConfigurationLoader.builder() + .source { reader } + .build() + .load() + + return configToMap(config) + } + + fun configToMap(node: ConfigurationNode): Map { + val result = mutableMapOf() + + for ((key, child) in node.childrenMap()) { + val value = when { + child.isMap -> configToMap(child) + child.isList -> child.childrenList().map { listNode -> + if (listNode.isMap) configToMap(listNode) + else listNode.raw() + } + + else -> child.raw() + } + + result[key.toString()] = value + } + + return result + } +} + +object CloudServerConfigProvider : ServerConfigProvider( + mapOf( + "standalone-config.yml" to YamlConfigParser, + "config.yml" to YamlConfigParser, + ), + listOf("ktor.bearer-token", "connection.database.password", "proxy.secret.manual-secret") +) \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spark/provider/CloudServerPingProvider.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spark/provider/CloudServerPingProvider.kt new file mode 100644 index 00000000..a29937cc --- /dev/null +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spark/provider/CloudServerPingProvider.kt @@ -0,0 +1,10 @@ +package dev.slne.surf.cloud.standalone.spark.provider + +import dev.slne.surf.cloud.standalone.server.serverManagerImpl +import me.lucko.spark.common.monitor.ping.PlayerPingProvider + +object CloudServerPingProvider : PlayerPingProvider { + override fun poll(): Map { + return serverManagerImpl.getPingData() + } +} \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spark/provider/CloudTickHook.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spark/provider/CloudTickHook.kt new file mode 100644 index 00000000..2c87f45a --- /dev/null +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spark/provider/CloudTickHook.kt @@ -0,0 +1,21 @@ +package dev.slne.surf.cloud.standalone.spark.provider + +import me.lucko.spark.common.tick.AbstractTickHook + +object CloudTickHook: AbstractTickHook() { + private var started = false + + fun tick() { + if (started) { + onTick() + } + } + + override fun start() { + started = true + } + + override fun close() { + started = false + } +} \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spark/provider/CloudTickReporter.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spark/provider/CloudTickReporter.kt new file mode 100644 index 00000000..4326ffb2 --- /dev/null +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spark/provider/CloudTickReporter.kt @@ -0,0 +1,21 @@ +package dev.slne.surf.cloud.standalone.spark.provider + +import me.lucko.spark.common.tick.AbstractTickReporter + +object CloudTickReporter: AbstractTickReporter() { + private var started = false + + fun tick(duration: Double) { + if (started) { + onTick(duration) + } + } + + override fun start() { + started = true + } + + override fun close() { + started = false + } +} \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spring/config/EhcacheConfig.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spring/config/EhcacheConfig.kt deleted file mode 100644 index 139f782b..00000000 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spring/config/EhcacheConfig.kt +++ /dev/null @@ -1,29 +0,0 @@ -package dev.slne.surf.cloud.standalone.spring.config - -import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer -import org.springframework.cache.annotation.EnableCaching -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import javax.cache.configuration.MutableConfiguration - -@Configuration -@EnableCaching -class EhcacheConfig { - - @Bean - fun cacheCustomizer(): JCacheManagerCustomizer { - return JCacheManagerCustomizer { - it.createCache( - "default-update-timestamps-region", - MutableConfiguration() - .setStatisticsEnabled(true) - ) - - it.createCache( - "default-query-results-region", - MutableConfiguration() - .setStatisticsEnabled(true) - ) - } - } -} \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spring/config/SurfCloudDataConfig.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spring/config/SurfCloudDataConfig.kt index 6924f973..a45d23bf 100644 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spring/config/SurfCloudDataConfig.kt +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spring/config/SurfCloudDataConfig.kt @@ -1,44 +1,22 @@ package dev.slne.surf.cloud.standalone.spring.config -import com.zaxxer.hikari.HikariDataSource -import dev.slne.surf.cloud.api.common.exceptions.ExitCodes -import dev.slne.surf.cloud.api.common.exceptions.FatalSurfError import dev.slne.surf.cloud.core.common.config.cloudConfig import dev.slne.surf.surfapi.core.api.util.logger -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails -import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration -import org.springframework.boot.jdbc.DataSourceBuilder import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Import -import org.springframework.context.annotation.Primary -import org.springframework.data.domain.Example -import org.springframework.data.domain.ExampleMatcher -import org.springframework.data.repository.query.QueryByExampleExecutor -import org.springframework.jdbc.datasource.DriverManagerDataSource -import org.springframework.orm.jpa.JpaTransactionManager -import java.sql.SQLException -import javax.sql.DataSource @Configuration -//@Import(DataSourcePoolMetadataProvidersConfiguration::class) @Import(TransactionAutoConfiguration::class) class SurfCloudDataConfig { companion object { private val log = logger() } -// @Bean -// fun transactionManager(): TransactionManager { -// JpaTransactionManager -// } - @Bean fun connectionDetails(): JdbcConnectionDetails { -// JpaTransactionManager() - val databaseConfig = cloudConfig.connectionConfig.databaseConfig return object : JdbcConnectionDetails { @@ -55,78 +33,4 @@ class SurfCloudDataConfig { } } } - -// @Configuration(proxyBeanMethods = false) -// class DataSourceConfig { -// -// @Configuration(proxyBeanMethods = false) -// @ConditionalOnClass(HikariDataSource::class) -// class Hikari { -// -// @Bean -// fun dataSource(connectionDetails: JdbcConnectionDetails): HikariDataSource { -// return createDataSource(connectionDetails, null) -// } -// } -// -// companion object { -// inline fun createDataSource( -// connectionDetails: JdbcConnectionDetails, -// classLoader: ClassLoader? -// ): T { -// return connectionDetails.run { -// DataSourceBuilder.create(classLoader) -// .type(T::class.java) -// .driverClassName(driverClassName) -// .url(jdbcUrl) -// .username(username) -// .password(password) -// .build() as T -// } -// } -// } -// -// @Bean -// @Primary -// fun dataSource(): DataSource { -// val databaseConfig = cloudConfig.connectionConfig.databaseConfig -// val dataSource = DriverManagerDataSource() -// -// with(dataSource) { -//// val driverClassName = databaseConfig.type.driver -//// setDriverClassName("org.mariadb.jdbc.Driver") -//// println("Driver class name: $driverClassName") -// username = databaseConfig.username -// password = databaseConfig.password -// url = databaseConfig.url -// } -// -// validateDatasource(dataSource) -// -// return dataSource -// } -// -// -// private fun validateDatasource(dataSource: DriverManagerDataSource) { -// try { -// dataSource.connection.use { -// log.atFine() -// .log("Successfully connected to the database.") -// } -// } catch (e: SQLException) { -// throw FatalSurfError { -// simpleErrorMessage("Failed to tryEstablishConnection0 to the database.") -// detailedErrorMessage("The database connection could not be established using the provided configuration.") -// cause(e) -// additionalInformation("Database URL: ${dataSource.url}") -// additionalInformation("Username: ${dataSource.username}") -// additionalInformation("Password set: ${dataSource.password != null}") -// possibleSolution("Check if the database server is running and accessible.") -// possibleSolution("Verify that the database URL, username, and password are correct.") -// possibleSolution("Ensure that the database driver (org.mariadb.jdbc.Driver) is compatible.") -// exitCode(ExitCodes.UNABLE_TO_CONNECT_TO_DATABASE) -// } -// } -// } -// } } diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spring/entities/player/CloudPlayerEntity.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spring/entities/player/CloudPlayerEntity.kt deleted file mode 100644 index fe87d30e..00000000 --- a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/spring/entities/player/CloudPlayerEntity.kt +++ /dev/null @@ -1,44 +0,0 @@ -package dev.slne.surf.cloud.standalone.spring.entities.player - -import jakarta.persistence.* -import org.hibernate.annotations.JdbcTypeCode -import org.hibernate.annotations.TimeZoneStorage -import org.hibernate.annotations.TimeZoneStorageType -import org.hibernate.type.SqlTypes -import java.io.Serial -import java.io.Serializable -import java.net.Inet4Address -import java.time.ZonedDateTime -import java.util.* - -//@Entity -//@Table(name = "cloud_player") -//class CloudPlayerEntity : Serializable { -// -// @Id -// @GeneratedValue(strategy = GenerationType.IDENTITY) -// @JdbcTypeCode(SqlTypes.BIGINT) -// @Column(nullable = false) -// var id: Long? = null -// -// @JdbcTypeCode(SqlTypes.CHAR) -// @Column(name = "uuid", nullable = false, unique = true, length = 36) -// var uuid: UUID? = null -// -// @JdbcTypeCode(SqlTypes.CHAR) -// @Column(name = "last_server") -// var lastServer: String? = null -// -// @TimeZoneStorage(TimeZoneStorageType.AUTO) -// @Column(name = "last_seen") -// var lastSeen: ZonedDateTime? = null -// -// @Column(name = "last_ip_address") -// var lastIpAddress: Inet4Address? = null -// -// -// companion object { -// @Serial -// private const val serialVersionUID: Long = -7433875414223638042L -// } -//} \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/sync/SyncRegistryImpl.kt b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/sync/SyncRegistryImpl.kt new file mode 100644 index 00000000..6ae332a3 --- /dev/null +++ b/surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/sync/SyncRegistryImpl.kt @@ -0,0 +1,108 @@ +package dev.slne.surf.cloud.standalone.sync + +import com.google.auto.service.AutoService +import dev.slne.surf.cloud.api.common.netty.packet.NettyPacket +import dev.slne.surf.cloud.api.common.sync.SyncRegistry +import dev.slne.surf.cloud.core.common.netty.network.ConnectionImpl +import dev.slne.surf.cloud.core.common.netty.network.protocol.running.SyncSetDeltaPacket +import dev.slne.surf.cloud.core.common.netty.network.protocol.running.SyncValueChangePacket +import dev.slne.surf.cloud.core.common.sync.BasicSyncValue +import dev.slne.surf.cloud.core.common.sync.CommonSyncRegistryImpl +import dev.slne.surf.cloud.core.common.sync.SyncSetImpl +import dev.slne.surf.cloud.core.common.util.bean +import dev.slne.surf.cloud.standalone.netty.server.NettyServerImpl +import dev.slne.surf.surfapi.core.api.util.logger +import dev.slne.surf.surfapi.core.api.util.mutableObject2LongMapOf +import dev.slne.surf.surfapi.core.api.util.synchronize + +@AutoService(SyncRegistry::class) +class SyncRegistryImpl : CommonSyncRegistryImpl() { + private val log = logger() + private val lastChangeIds = mutableObject2LongMapOf().synchronize() + + override fun afterChange(syncValue: BasicSyncValue<*>) { + super.afterChange(syncValue) + broadcast(SyncValueChangePacket(null, syncValue)) + } + + override fun afterChange( + syncSet: SyncSetImpl, + added: Boolean, + changeId: Long, + element: T + ) { + super.afterChange(syncSet, added, changeId, element) + val lastChangeId = lastChangeIds.getLong(syncSet.id) + if (changeId <= lastChangeId) { + log.atInfo() + .log("Ignoring stale SyncSetDeltaPacket for set '${syncSet.id}' with changeId $changeId, last known changeId is $lastChangeId") + return + } + lastChangeIds[syncSet.id] = changeId + broadcast(SyncSetDeltaPacket(syncSet, added, changeId, element)) + } + + fun handleChangePacket(packet: SyncValueChangePacket, sender: ConnectionImpl) { + if (!packet.registered) return + val syncId = packet.syncId + val value = packet.value + + updateSyncValue(syncId, value) + broadcast(packet, sender) + } + + fun handleSyncSetDeltaPacket(packet: SyncSetDeltaPacket, sender: ConnectionImpl) { + if (!packet.registered) return + val set = getSet(packet.setId) + if (set == null) { + log.atWarning() + .log("SyncSet with id '${packet.setId}' not found, cannot apply delta") + return + } + + val lastChangeId = lastChangeIds.getLong(packet.setId) + if (packet.changeId <= lastChangeId) { + log.atInfo() + .log("Ignoring stale SyncSetDeltaPacket for set '${packet.setId}' with changeId ${packet.changeId}, last known changeId is $lastChangeId") + return + } + + if (packet.added) { + set.addInternal(packet.element) + } else { + set.removeInternal(packet.element) + } + lastChangeIds[packet.setId] = packet.changeId + + broadcast(packet, sender) + + } + + fun prepareBatchSyncValues(): Map> { + require(frozen) { "SyncRegistry is not frozen, cannot prepare batch" } + + return syncValues.toMap() + } + + fun prepareBatchSyncSets(): Map> { + require(frozen) { "SyncRegistry is not frozen, cannot prepare batch sync set" } + return syncSets.toMap() + } + + private fun broadcast(packet: NettyPacket, sender: ConnectionImpl? = null) { + if (sender == null) { + bean().connection.broadcast(packet) + } else { + val protocols = packet.protocols + for (connection in bean().connection.connections) { + if (connection.outboundProtocolInfo.id !in protocols) continue + if (connection == sender) continue // Skip the sender + connection.send(packet) + } + } + } + + companion object { + val instance by lazy { CommonSyncRegistryImpl.instance as SyncRegistryImpl } + } +} \ No newline at end of file diff --git a/surf-cloud-standalone/src/main/resources/aj.properties b/surf-cloud-standalone/src/main/resources/aj.properties new file mode 100644 index 00000000..3a247f2b --- /dev/null +++ b/surf-cloud-standalone/src/main/resources/aj.properties @@ -0,0 +1,2 @@ +# AspectJ load-time weaving config +weaver.ignoreMissingDependencies=true diff --git a/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/TestStandaloneBootstrap.kt b/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/TestStandaloneBootstrap.kt similarity index 92% rename from surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/TestStandaloneBootstrap.kt rename to surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/TestStandaloneBootstrap.kt index 03c3d15b..df00bbbb 100644 --- a/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/TestStandaloneBootstrap.kt +++ b/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/TestStandaloneBootstrap.kt @@ -1,4 +1,4 @@ -package dev.slne.surf.cloud.standalone.test +package dev.slne.surf.cloudtest.standalone.test import dev.slne.surf.cloud.api.common.CloudInstance import dev.slne.surf.cloud.api.common.startSpringApplication diff --git a/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/TestStandaloneLoader.kt b/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/TestStandaloneLoader.kt similarity index 93% rename from surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/TestStandaloneLoader.kt rename to surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/TestStandaloneLoader.kt index a9e4a67f..cb1030a3 100644 --- a/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/TestStandaloneLoader.kt +++ b/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/TestStandaloneLoader.kt @@ -1,4 +1,4 @@ -package dev.slne.surf.cloud.standalone.test +package dev.slne.surf.cloudtest.standalone.test import dev.slne.surf.cloud.api.server.plugin.loader.PluginClasspathBuilder import dev.slne.surf.cloud.api.server.plugin.loader.StandalonePluginLoader diff --git a/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/TestStandalonePlugin.kt b/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/TestStandalonePlugin.kt similarity index 84% rename from surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/TestStandalonePlugin.kt rename to surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/TestStandalonePlugin.kt index a756ae1b..39668725 100644 --- a/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/TestStandalonePlugin.kt +++ b/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/TestStandalonePlugin.kt @@ -1,7 +1,9 @@ -package dev.slne.surf.cloud.standalone.test +package dev.slne.surf.cloudtest.standalone.test import dev.slne.surf.cloud.api.server.plugin.KtorPlugin import dev.slne.surf.cloud.api.server.plugin.StandalonePlugin +import dev.slne.surf.cloud.api.server.plugin.utils.bean +import dev.slne.surf.cloudtest.standalone.test.sync.SyncValueTest import io.ktor.server.routing.* import kotlinx.coroutines.delay import org.apache.commons.io.FileSystemUtils @@ -28,6 +30,8 @@ class TestStandalonePlugin : StandalonePlugin(), KtorPlugin { logger.info("Hello from coroutine after delay") delay(5.seconds) } + + bean().test() } override suspend fun disable() { diff --git a/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/TestStandaloneSpringApplication.kt b/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/TestStandaloneSpringApplication.kt similarity index 82% rename from surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/TestStandaloneSpringApplication.kt rename to surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/TestStandaloneSpringApplication.kt index ebd3432f..a119c44b 100644 --- a/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/TestStandaloneSpringApplication.kt +++ b/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/TestStandaloneSpringApplication.kt @@ -1,4 +1,4 @@ -package dev.slne.surf.cloud.standalone.test +package dev.slne.surf.cloudtest.standalone.test import dev.slne.surf.cloud.api.common.SurfCloudApplication import dev.slne.surf.cloud.api.server.plugin.AdditionalStandaloneConfiguration diff --git a/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/db/TestExposedService.kt b/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/db/TestExposedService.kt similarity index 98% rename from surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/db/TestExposedService.kt rename to surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/db/TestExposedService.kt index 5737db78..d8360a8b 100644 --- a/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/db/TestExposedService.kt +++ b/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/db/TestExposedService.kt @@ -1,4 +1,4 @@ -package dev.slne.surf.cloud.standalone.test.db +package dev.slne.surf.cloudtest.standalone.test.db import dev.slne.surf.cloud.api.server.plugin.CoroutineTransactional import dev.slne.surf.cloud.api.server.plugin.utils.currentDb diff --git a/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/db/TestExposedTable.kt b/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/db/TestExposedTable.kt similarity index 93% rename from surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/db/TestExposedTable.kt rename to surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/db/TestExposedTable.kt index 8fe25d2f..bc15a227 100644 --- a/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/db/TestExposedTable.kt +++ b/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/db/TestExposedTable.kt @@ -1,4 +1,4 @@ -package dev.slne.surf.cloud.standalone.test.db +package dev.slne.surf.cloudtest.standalone.test.db import dev.slne.surf.cloud.api.server.exposed.table.AuditableLongEntity import dev.slne.surf.cloud.api.server.exposed.table.AuditableLongEntityClass diff --git a/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/packet/TestPacket.kt b/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/packet/TestPacket.kt similarity index 93% rename from surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/packet/TestPacket.kt rename to surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/packet/TestPacket.kt index 906e0a61..a021904f 100644 --- a/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/packet/TestPacket.kt +++ b/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/packet/TestPacket.kt @@ -1,4 +1,4 @@ -package dev.slne.surf.cloud.standalone.test.packet +package dev.slne.surf.cloudtest.standalone.test.packet import dev.slne.surf.cloud.api.common.meta.PacketCodec import dev.slne.surf.cloud.api.common.meta.SurfNettyPacket diff --git a/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/punish/IpAddressPunishmentHandler.kt b/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/punish/IpAddressPunishmentHandler.kt similarity index 97% rename from surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/punish/IpAddressPunishmentHandler.kt rename to surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/punish/IpAddressPunishmentHandler.kt index 3c5a5a30..47ff2a02 100644 --- a/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/punish/IpAddressPunishmentHandler.kt +++ b/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/punish/IpAddressPunishmentHandler.kt @@ -1,4 +1,4 @@ -package dev.slne.surf.cloud.standalone.test.punish +package dev.slne.surf.cloudtest.standalone.test.punish import dev.slne.surf.cloud.api.common.player.OfflineCloudPlayer import dev.slne.surf.cloud.api.common.player.punishment.CloudPlayerPunishmentManager diff --git a/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/punish/TestCloudEventListener.kt b/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/punish/TestCloudEventListener.kt similarity index 92% rename from surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/punish/TestCloudEventListener.kt rename to surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/punish/TestCloudEventListener.kt index 45f643c4..679738ad 100644 --- a/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/test/punish/TestCloudEventListener.kt +++ b/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/punish/TestCloudEventListener.kt @@ -1,4 +1,4 @@ -package dev.slne.surf.cloud.standalone.test.punish +package dev.slne.surf.cloudtest.standalone.test.punish import dev.slne.surf.cloud.api.common.event.CloudEvent import dev.slne.surf.cloud.api.common.event.CloudEventHandler diff --git a/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/sync/SyncValueTest.kt b/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/sync/SyncValueTest.kt new file mode 100644 index 00000000..2307a745 --- /dev/null +++ b/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/kotlin/dev/slne/surf/cloudtest/standalone/test/sync/SyncValueTest.kt @@ -0,0 +1,68 @@ +package dev.slne.surf.cloudtest.standalone.test.sync + +import dev.slne.surf.cloud.api.common.sync.SyncValue +import kotlinx.serialization.Serializable +import org.springframework.stereotype.Component +import kotlin.time.Duration.Companion.seconds + +@Component +class SyncValueTest { + val syncValue = SyncValue("basic_sync_value", BasicSyncValue.DEFAULT) + var syncValueDelegate by syncValue + + val rateLimitedSyncValue = SyncValue("rate_limited_sync_value", BasicSyncValue.DEFAULT) + .rateLimited(5.seconds) + var rateLimitedSyncValueDelegate by rateLimitedSyncValue + + fun test() { + syncValue.subscribe { old, new -> + println("SyncValue changed from $old to $new") + } + + syncValueDelegate = BasicSyncValue( + id = "new_id", + name = "new_name", + value = "new_value" + ) + + println("Current SyncValue: $syncValueDelegate") + + rateLimitedSyncValue.subscribe { old, new -> + println("RateLimitedSyncValue changed from $old to $new") + } + + rateLimitedSyncValueDelegate = BasicSyncValue( + id = "rate_limited_new_id", + name = "rate_limited_new_name", + value = "rate_limited_new_value" + ) + + println("Current RateLimitedSyncValue: $rateLimitedSyncValueDelegate") + + for (i in 1..5) { + rateLimitedSyncValueDelegate = BasicSyncValue( + id = "rate_limited_id_$i", + name = "rate_limited_name_$i", + value = "rate_limited_value_$i" + ) + } + + println("Current RateLimitedSyncValue after multiple updates: $rateLimitedSyncValueDelegate") + } + + + @Serializable + data class BasicSyncValue( + val id: String, + val name: String, + val value: String + ) { + companion object { + val DEFAULT = BasicSyncValue( + id = "default_id", + name = "default_name", + value = "default_value" + ) + } + } +} \ No newline at end of file diff --git a/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/resources/standalone-plugin.yml b/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/resources/standalone-plugin.yml index e0022273..75d511fa 100644 --- a/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/resources/standalone-plugin.yml +++ b/surf-cloud-test-plugin/surf-cloud-test-standalone/src/main/resources/standalone-plugin.yml @@ -1,7 +1,7 @@ name: surf-cloud-test-standalone -main: 'dev.slne.surf.cloud.standalone.test.TestStandalonePlugin' -bootstrapper: 'dev.slne.surf.cloud.standalone.test.TestStandaloneBootstrap' -loader: 'dev.slne.surf.cloud.standalone.test.TestStandaloneLoader' +main: 'dev.slne.surf.cloudtest.standalone.test.TestStandalonePlugin' +bootstrapper: 'dev.slne.surf.cloudtest.standalone.test.TestStandaloneBootstrap' +loader: 'dev.slne.surf.cloudtest.standalone.test.TestStandaloneLoader' version: 1.21.4-1.0.0-SNAPSHOT description: 'Test standalone plugin' authors: diff --git a/surf-cloud-velocity/build.gradle.kts b/surf-cloud-velocity/build.gradle.kts index 4457f877..b8f830cb 100644 --- a/surf-cloud-velocity/build.gradle.kts +++ b/surf-cloud-velocity/build.gradle.kts @@ -1,4 +1,5 @@ plugins { + `exclude-kotlin` id("dev.slne.surf.surfapi.gradle.velocity") } @@ -20,4 +21,10 @@ configurations { runtimeClasspath { exclude(group = "org.reactivestreams", module = "reactive-streams") } +} + +kotlin { + compilerOptions { + optIn.add("dev.slne.surf.cloud.api.common.util.annotation.InternalApi") + } } \ No newline at end of file diff --git a/surf-cloud-velocity/src/main/kotlin/dev/slne/surf/cloud/velocity/VelocityMain.kt b/surf-cloud-velocity/src/main/kotlin/dev/slne/surf/cloud/velocity/VelocityMain.kt index 3823d369..c86d7ebb 100644 --- a/surf-cloud-velocity/src/main/kotlin/dev/slne/surf/cloud/velocity/VelocityMain.kt +++ b/surf-cloud-velocity/src/main/kotlin/dev/slne/surf/cloud/velocity/VelocityMain.kt @@ -13,8 +13,6 @@ import dev.slne.surf.cloud.core.common.coreCloudInstance import dev.slne.surf.cloud.core.common.handleEventuallyFatalError import kotlinx.coroutines.runBlocking import java.nio.file.Path -import kotlin.Nothing -import kotlin.system.exitProcess //@Plugin( // id = "surf-cloud-velocity", diff --git a/surf-cloud-velocity/src/main/kotlin/dev/slne/surf/cloud/velocity/listener/player/ConnectionListener.kt b/surf-cloud-velocity/src/main/kotlin/dev/slne/surf/cloud/velocity/listener/player/ConnectionListener.kt index b39930dd..f7d34b49 100644 --- a/surf-cloud-velocity/src/main/kotlin/dev/slne/surf/cloud/velocity/listener/player/ConnectionListener.kt +++ b/surf-cloud-velocity/src/main/kotlin/dev/slne/surf/cloud/velocity/listener/player/ConnectionListener.kt @@ -1,20 +1,26 @@ package dev.slne.surf.cloud.velocity.listener.player +import com.velocitypowered.api.event.PostOrder import com.velocitypowered.api.event.ResultedEvent.ComponentResult import com.velocitypowered.api.event.Subscribe import com.velocitypowered.api.event.connection.DisconnectEvent import com.velocitypowered.api.event.connection.LoginEvent +import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent import dev.slne.surf.cloud.api.client.netty.packet.fireAndAwait import dev.slne.surf.cloud.api.client.netty.packet.fireAndForget +import dev.slne.surf.cloud.api.client.velocity.server.toRegisteredServer +import dev.slne.surf.cloud.api.common.player.task.PrePlayerJoinTask.Result +import dev.slne.surf.cloud.api.common.server.CloudServerManager import dev.slne.surf.cloud.core.common.data.CloudPersistentData import dev.slne.surf.cloud.core.common.messages.MessageManager import dev.slne.surf.cloud.core.common.netty.network.protocol.running.PlayerConnectToServerPacket import dev.slne.surf.cloud.core.common.netty.network.protocol.running.PlayerDisconnectFromServerPacket -import dev.slne.surf.cloud.core.common.player.task.PrePlayerJoinTask.Result +import dev.slne.surf.cloud.velocity.proxy import org.springframework.stereotype.Component import java.net.Inet4Address import kotlin.time.Duration.Companion.seconds +@Suppress("unused") @Component class ConnectionListener { @@ -45,4 +51,18 @@ class ConnectionListener { true ).fireAndForget() } + + @Subscribe(order = PostOrder.LAST) + suspend fun onPlayerChooseInitialServer(event: PlayerChooseInitialServerEvent) { + if (event.initialServer.isPresent) return + + val lowestLobby = CloudServerManager.retrieveServers() + .filter { it.lobby } + .minByOrNull { it.currentPlayerCount } + ?.toRegisteredServer(proxy) + + if (lowestLobby != null) { + event.setInitialServer(lowestLobby) + } + } } \ No newline at end of file diff --git a/surf-cloud-velocity/src/main/kotlin/dev/slne/surf/cloud/velocity/netty/network/VelocitySpecificPacketListenerExtension.kt b/surf-cloud-velocity/src/main/kotlin/dev/slne/surf/cloud/velocity/netty/network/VelocitySpecificPacketListenerExtension.kt index d14f5f84..b775d8eb 100644 --- a/surf-cloud-velocity/src/main/kotlin/dev/slne/surf/cloud/velocity/netty/network/VelocitySpecificPacketListenerExtension.kt +++ b/surf-cloud-velocity/src/main/kotlin/dev/slne/surf/cloud/velocity/netty/network/VelocitySpecificPacketListenerExtension.kt @@ -5,10 +5,17 @@ import com.velocitypowered.api.proxy.server.ServerInfo import dev.slne.surf.cloud.api.common.player.teleport.TeleportCause import dev.slne.surf.cloud.api.common.player.teleport.TeleportFlag import dev.slne.surf.cloud.api.common.player.teleport.TeleportLocation +import dev.slne.surf.cloud.api.common.util.observer.observingFlow import dev.slne.surf.cloud.core.client.netty.network.PlatformSpecificPacketListenerExtension +import dev.slne.surf.cloud.core.client.server.ClientCloudServerImpl +import dev.slne.surf.cloud.core.common.coroutines.CommonObservableScope import dev.slne.surf.cloud.core.common.netty.network.protocol.running.RegistrationInfo import dev.slne.surf.cloud.core.common.netty.network.protocol.running.ServerboundTransferPlayerPacketResponse.Status import dev.slne.surf.cloud.velocity.proxy +import dev.slne.surf.cloud.velocity.reflection.VelocityConfigurationProxy +import dev.slne.surf.surfapi.core.api.util.logger +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.future.await import net.kyori.adventure.text.Component import java.net.InetSocketAddress @@ -18,6 +25,11 @@ import org.springframework.stereotype.Component as SpringComponent @SpringComponent class VelocitySpecificPacketListenerExtension : PlatformSpecificPacketListenerExtension { + private val log = logger() + + override val playAddress: InetSocketAddress + get() = proxy.boundAddress + override fun isServerManagedByThisProxy(address: InetSocketAddress) = proxy.allServers.any { it.serverInfo.address == address } // TODO: Check if this is correct @@ -58,11 +70,25 @@ class VelocitySpecificPacketListenerExtension : PlatformSpecificPacketListenerEx error("Teleporting players is not supported on Velocity") } - override fun registerCloudServersToProxy(servers: Array) { - servers.map { (name, address) -> ServerInfo(name, address) } + override fun registerCloudServersToProxy(packets: Array) { + packets.map { (name, address) -> ServerInfo(name, address) } .forEach { proxy.registerServer(it) } } + override fun registerCloudServerToProxy(client: ClientCloudServerImpl) { + val serverInfo = ServerInfo(client.name, client.playAddress) + log.atInfo() + .log("Registering server %s to proxy", serverInfo) + proxy.registerServer(serverInfo) + } + + override fun unregisterCloudServerFromProxy(client: ClientCloudServerImpl) { + val info = ServerInfo(client.name, client.playAddress) + log.atInfo() + .log("Unregistering server %s from proxy", info) + proxy.unregisterServer(info) + } + override suspend fun teleportPlayerToPlayer( uuid: UUID, target: UUID @@ -81,4 +107,24 @@ class VelocitySpecificPacketListenerExtension : PlatformSpecificPacketListenerEx override fun shutdown() { proxy.shutdown() } + + override fun setVelocitySecret(secret: ByteArray) { + VelocitySecretManager.currentVelocitySecret = secret + } + + object VelocitySecretManager { + @Volatile + var currentVelocitySecret = + VelocityConfigurationProxy.instance.getForwardingSecret(proxy.configuration) + + init { + observingFlow({ VelocityConfigurationProxy.instance.getForwardingSecret(proxy.configuration) }) + .onEach { remote -> + if (!remote.contentEquals(currentVelocitySecret)) { + VelocityConfigurationProxy.instance.setForwardingSecret(proxy.configuration, currentVelocitySecret) + } + } + .launchIn(CommonObservableScope) + } + } } \ No newline at end of file diff --git a/surf-cloud-velocity/src/main/kotlin/dev/slne/surf/cloud/velocity/player/ValidateVelocityPlayerJoin.kt b/surf-cloud-velocity/src/main/kotlin/dev/slne/surf/cloud/velocity/player/ValidateVelocityPlayerJoin.kt new file mode 100644 index 00000000..a96b4b45 --- /dev/null +++ b/surf-cloud-velocity/src/main/kotlin/dev/slne/surf/cloud/velocity/player/ValidateVelocityPlayerJoin.kt @@ -0,0 +1,22 @@ +package dev.slne.surf.cloud.velocity.player + +import dev.slne.surf.cloud.api.common.player.OfflineCloudPlayer +import dev.slne.surf.cloud.api.common.player.task.PrePlayerJoinTask +import dev.slne.surf.cloud.core.common.messages.MessageManager +import dev.slne.surf.cloud.velocity.proxy +import org.springframework.core.annotation.Order +import org.springframework.stereotype.Component as SpringComponent + +@SpringComponent +@Order(PrePlayerJoinTask.VELOCITY_PLAYER_JOIN_VALIDATION) +class ValidateVelocityPlayerJoin : PrePlayerJoinTask { + override suspend fun preJoin(player: OfflineCloudPlayer): PrePlayerJoinTask.Result { + return if (proxy.allServers.isEmpty()) { + PrePlayerJoinTask.Result.DENIED( + MessageManager.noServersAvailableToJoin + ) + } else { + PrePlayerJoinTask.Result.ALLOWED + } + } +} \ No newline at end of file diff --git a/surf-cloud-velocity/src/main/kotlin/dev/slne/surf/cloud/velocity/reflection/VelocityConfigurationProxy.kt b/surf-cloud-velocity/src/main/kotlin/dev/slne/surf/cloud/velocity/reflection/VelocityConfigurationProxy.kt new file mode 100644 index 00000000..fde73472 --- /dev/null +++ b/surf-cloud-velocity/src/main/kotlin/dev/slne/surf/cloud/velocity/reflection/VelocityConfigurationProxy.kt @@ -0,0 +1,21 @@ +package dev.slne.surf.cloud.velocity.reflection + +import com.velocitypowered.api.proxy.config.ProxyConfig +import dev.slne.surf.surfapi.core.api.reflection.Field +import dev.slne.surf.surfapi.core.api.reflection.SurfProxy +import dev.slne.surf.surfapi.core.api.reflection.createProxy +import dev.slne.surf.surfapi.core.api.reflection.surfReflection + +@SurfProxy(qualifiedName = "com.velocitypowered.proxy.config.VelocityConfiguration") +interface VelocityConfigurationProxy { + + @Field(name = "forwardingSecret", type = Field.Type.GETTER) + fun getForwardingSecret(instance: ProxyConfig): ByteArray + + @Field(name = "forwardingSecret", type = Field.Type.SETTER) + fun setForwardingSecret(instance: ProxyConfig, value: ByteArray) + + companion object { + val instance = surfReflection.createProxy() + } +} \ No newline at end of file