From 0ef06597f18fd8a233cdac77d74a118fbc49ebb5 Mon Sep 17 00:00:00 2001 From: chris Date: Wed, 11 Sep 2024 02:00:55 +0800 Subject: [PATCH] Support neoforge platform, move to using architectury (#125) * rebase * split fabric/neoforge specific code * more progress * dependency configuration * exclude more junk * neo, you make me go insane * neoforge seems to work! * some minor cleanup, add neoforge command module * mixin config plugin shenanigans * fix language strings loading * some cleanup, yeet Jenkinsfile * proper repository declaration using plugin, target jitpack branch * oops * address reviews * Update for 1.21 * some minor fixes * Fix modrinth task, add floodgate version command mixin to disable version checking, update to loom 1.7, update cloud, update floodgate core to not use my branch * oops * what on earth is going on now * neoforge works again!!!!!!!!! * Address review, dont rely on locals in mixin * modrinth version/name changes, similar to geyser * Update README.md Co-authored-by: Konicai <71294714+Konicai@users.noreply.github.com> * Improve handling of PayloadRegistrar this took years off my life * address review * Move blossom version declaration to libs.versions.toml * remove unused versions from catalogue * Only run modrinth task if successful & on Geyser repo * cleanup & fix gh actions building/archiving * run and uses are different steps --------- Co-authored-by: Konicai <71294714+Konicai@users.noreply.github.com> --- .github/workflows/publish.yml | 22 ++- README.md | 13 +- build-logic/build.gradle.kts | 23 +++ build-logic/settings.gradle.kts | 11 ++ build-logic/src/main/kotlin/LibsAccessor.kt | 6 + build-logic/src/main/kotlin/extensions.kt | 36 ++++ ...oodgate-modded.base-conventions.gradle.kts | 37 +++++ .../floodgate-modded.build-logic.gradle.kts | 14 ++ ...ate-modded.platform-conventions.gradle.kts | 134 +++++++++++++++ ...gate-modded.publish-conventions.gradle.kts | 15 ++ ...dgate-modded.shadow-conventions.gradle.kts | 37 +++++ build.gradle.kts | 157 ++---------------- fabric/build.gradle.kts | 46 +++++ fabric/gradle.properties | 1 + .../util/fabric/MixinConfigPluginImpl.java | 14 ++ .../platform/fabric/FabricFloodgateMod.java | 54 ++++++ .../listener/FabricEventRegistration.java | 14 ++ .../fabric}/module/FabricCommandModule.java | 13 +- .../fabric/module/FabricPlatformModule.java | 40 +++++ .../FabricPluginMessageRegistration.java | 12 +- .../FabricPluginMessageUtils.java | 21 +-- fabric/src/main/resources/fabric.mod.json | 30 ++++ gradle.properties | 9 +- gradle/libs.versions.toml | 57 +++++++ gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 51 ++++-- gradlew.bat | 35 ++-- mod/build.gradle.kts | 33 ++++ .../geysermc/floodgate/mod/FloodgateMod.java | 59 +++++++ .../floodgate/mod}/MinecraftServerHolder.java | 2 +- .../floodgate/mod/data/ModDataAddon.java | 14 +- .../floodgate/mod/data/ModDataHandler.java | 24 +-- .../floodgate/mod/inject/ModInjector.java | 16 +- .../mod/listener/ModEventListener.java | 15 +- .../mod}/logger/Log4jFloodgateLogger.java | 8 +- .../floodgate/mod}/mixin/ChunkMapMixin.java | 2 +- .../mixin/ClientIntentionPacketMixin.java | 2 +- .../ClientIntentionPacketMixinInterface.java | 2 +- .../floodgate/mod}/mixin/ConnectionMixin.java | 2 +- .../mod/mixin/FloodgateUtilMixin.java | 49 ++++++ .../mod}/mixin/GeyserModInjectorMixin.java | 8 +- .../mixin/ServerConnectionListenerMixin.java | 10 +- .../floodgate/mod/module/ModAddonModule.java | 14 +- .../mod/module/ModListenerModule.java | 21 +++ .../mod/module/ModPlatformModule.java | 77 +++++++++ .../mod/pluginmessage/ModSkinApplier.java | 14 +- .../pluginmessage/payloads/FormPayload.java | 2 +- .../pluginmessage/payloads/PacketPayload.java | 2 +- .../pluginmessage/payloads/SkinPayload.java | 2 +- .../payloads/TransferPayload.java | 2 +- .../floodgate/mod/util/ModCommandUtil.java | 27 +-- .../mod/util/ModMixinConfigPlugin.java | 25 ++- .../floodgate/mod/util/ModPlatformUtils.java | 8 +- .../floodgate/mod/util/ModTemplateReader.java | 25 +++ .../main/resources/floodgate.accesswidener | 0 .../src}/main/resources/floodgate.mixins.json | 5 +- neoforge/build.gradle.kts | 61 +++++++ neoforge/gradle.properties | 1 + .../neoforge/ModMixinConfigPluginImpl.java | 13 ++ .../neoforge/NeoForgeFloodgateMod.java | 101 +++++++++++ .../listener/NeoForgeEventRegistration.java | 20 +++ .../mixin/NeoForgeFloodgateUtilMixin.java | 27 +++ .../module/NeoForgeCommandModule.java | 29 ++++ .../module/NeoForgePlatformModule.java | 46 +++++ .../NeoForgePluginMessageRegistration.java | 39 +++++ .../NeoForgePluginMessageUtils.java | 37 +++++ .../resources/META-INF/neoforge.mods.toml | 27 +++ .../resources/floodgate_neoforge.mixins.json | 12 ++ settings.gradle.kts | 30 +++- .../org/geysermc/floodgate/FabricMod.java | 63 ------- .../listener/FabricEventRegistration.java | 14 -- .../module/FabricListenerModule.java | 21 --- .../module/FabricPlatformModule.java | 107 ------------ .../floodgate/util/FabricTemplateReader.java | 38 ----- src/main/resources/fabric.mod.json | 31 ---- 76 files changed, 1507 insertions(+), 586 deletions(-) create mode 100644 build-logic/build.gradle.kts create mode 100644 build-logic/settings.gradle.kts create mode 100644 build-logic/src/main/kotlin/LibsAccessor.kt create mode 100644 build-logic/src/main/kotlin/extensions.kt create mode 100644 build-logic/src/main/kotlin/floodgate-modded.base-conventions.gradle.kts create mode 100644 build-logic/src/main/kotlin/floodgate-modded.build-logic.gradle.kts create mode 100644 build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts create mode 100644 build-logic/src/main/kotlin/floodgate-modded.publish-conventions.gradle.kts create mode 100644 build-logic/src/main/kotlin/floodgate-modded.shadow-conventions.gradle.kts create mode 100644 fabric/build.gradle.kts create mode 100644 fabric/gradle.properties create mode 100644 fabric/src/main/java/org/geysermc/floodgate/mod/util/fabric/MixinConfigPluginImpl.java create mode 100644 fabric/src/main/java/org/geysermc/floodgate/platform/fabric/FabricFloodgateMod.java create mode 100644 fabric/src/main/java/org/geysermc/floodgate/platform/fabric/listener/FabricEventRegistration.java rename {src/main/java/org/geysermc/floodgate => fabric/src/main/java/org/geysermc/floodgate/platform/fabric}/module/FabricCommandModule.java (64%) create mode 100644 fabric/src/main/java/org/geysermc/floodgate/platform/fabric/module/FabricPlatformModule.java rename {src/main/java/org/geysermc/floodgate => fabric/src/main/java/org/geysermc/floodgate/platform/fabric}/pluginmessage/FabricPluginMessageRegistration.java (85%) rename {src/main/java/org/geysermc/floodgate => fabric/src/main/java/org/geysermc/floodgate/platform/fabric}/pluginmessage/FabricPluginMessageUtils.java (61%) create mode 100644 fabric/src/main/resources/fabric.mod.json create mode 100644 gradle/libs.versions.toml create mode 100644 mod/build.gradle.kts create mode 100644 mod/src/main/java/org/geysermc/floodgate/mod/FloodgateMod.java rename {src/main/java/org/geysermc/floodgate => mod/src/main/java/org/geysermc/floodgate/mod}/MinecraftServerHolder.java (92%) rename src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java => mod/src/main/java/org/geysermc/floodgate/mod/data/ModDataAddon.java (78%) rename src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java => mod/src/main/java/org/geysermc/floodgate/mod/data/ModDataHandler.java (89%) rename src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java => mod/src/main/java/org/geysermc/floodgate/mod/inject/ModInjector.java (78%) rename src/main/java/org/geysermc/floodgate/listener/FabricEventListener.java => mod/src/main/java/org/geysermc/floodgate/mod/listener/ModEventListener.java (55%) rename {src/main/java/org/geysermc/floodgate => mod/src/main/java/org/geysermc/floodgate/mod}/logger/Log4jFloodgateLogger.java (88%) rename {src/main/java/org/geysermc/floodgate => mod/src/main/java/org/geysermc/floodgate/mod}/mixin/ChunkMapMixin.java (88%) rename {src/main/java/org/geysermc/floodgate => mod/src/main/java/org/geysermc/floodgate/mod}/mixin/ClientIntentionPacketMixin.java (92%) rename {src/main/java/org/geysermc/floodgate => mod/src/main/java/org/geysermc/floodgate/mod}/mixin/ClientIntentionPacketMixinInterface.java (90%) rename {src/main/java/org/geysermc/floodgate => mod/src/main/java/org/geysermc/floodgate/mod}/mixin/ConnectionMixin.java (87%) create mode 100644 mod/src/main/java/org/geysermc/floodgate/mod/mixin/FloodgateUtilMixin.java rename {src/main/java/org/geysermc/floodgate => mod/src/main/java/org/geysermc/floodgate/mod}/mixin/GeyserModInjectorMixin.java (71%) rename {src/main/java/org/geysermc/floodgate => mod/src/main/java/org/geysermc/floodgate/mod}/mixin/ServerConnectionListenerMixin.java (73%) rename src/main/java/org/geysermc/floodgate/module/FabricAddonModule.java => mod/src/main/java/org/geysermc/floodgate/mod/module/ModAddonModule.java (63%) create mode 100644 mod/src/main/java/org/geysermc/floodgate/mod/module/ModListenerModule.java create mode 100644 mod/src/main/java/org/geysermc/floodgate/mod/module/ModPlatformModule.java rename src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java => mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/ModSkinApplier.java (90%) rename {src/main/java/org/geysermc/floodgate => mod/src/main/java/org/geysermc/floodgate/mod}/pluginmessage/payloads/FormPayload.java (94%) rename {src/main/java/org/geysermc/floodgate => mod/src/main/java/org/geysermc/floodgate/mod}/pluginmessage/payloads/PacketPayload.java (94%) rename {src/main/java/org/geysermc/floodgate => mod/src/main/java/org/geysermc/floodgate/mod}/pluginmessage/payloads/SkinPayload.java (94%) rename {src/main/java/org/geysermc/floodgate => mod/src/main/java/org/geysermc/floodgate/mod}/pluginmessage/payloads/TransferPayload.java (94%) rename src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java => mod/src/main/java/org/geysermc/floodgate/mod/util/ModCommandUtil.java (82%) rename src/main/java/org/geysermc/floodgate/util/MixinConfigPlugin.java => mod/src/main/java/org/geysermc/floodgate/mod/util/ModMixinConfigPlugin.java (56%) rename src/main/java/org/geysermc/floodgate/util/FabricPlatformUtils.java => mod/src/main/java/org/geysermc/floodgate/mod/util/ModPlatformUtils.java (67%) create mode 100644 mod/src/main/java/org/geysermc/floodgate/mod/util/ModTemplateReader.java rename {src => mod/src}/main/resources/floodgate.accesswidener (100%) rename {src => mod/src}/main/resources/floodgate.mixins.json (69%) create mode 100644 neoforge/build.gradle.kts create mode 100644 neoforge/gradle.properties create mode 100644 neoforge/src/main/java/org/geysermc/floodgate/mod/util/neoforge/ModMixinConfigPluginImpl.java create mode 100644 neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/NeoForgeFloodgateMod.java create mode 100644 neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/listener/NeoForgeEventRegistration.java create mode 100644 neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/mixin/NeoForgeFloodgateUtilMixin.java create mode 100644 neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/module/NeoForgeCommandModule.java create mode 100644 neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/module/NeoForgePlatformModule.java create mode 100644 neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/pluginmessage/NeoForgePluginMessageRegistration.java create mode 100644 neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/pluginmessage/NeoForgePluginMessageUtils.java create mode 100644 neoforge/src/main/resources/META-INF/neoforge.mods.toml create mode 100644 neoforge/src/main/resources/floodgate_neoforge.mixins.json delete mode 100644 src/main/java/org/geysermc/floodgate/FabricMod.java delete mode 100644 src/main/java/org/geysermc/floodgate/listener/FabricEventRegistration.java delete mode 100644 src/main/java/org/geysermc/floodgate/module/FabricListenerModule.java delete mode 100644 src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java delete mode 100644 src/main/java/org/geysermc/floodgate/util/FabricTemplateReader.java delete mode 100644 src/main/resources/fabric.mod.json diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index bbb2057..08b4c8b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -5,23 +5,27 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@72f2cec99f417b1a1c5e2e88945068983b7965f9 - - uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 - - uses: actions/setup-java@4075bfc1b51bf22876335ae1cd589602d60d8758 + - name: Setup Gradle + uses: GeyserMC/actions/setup-gradle-composite@master with: - distribution: 'temurin' - java-version: 21 + setup-java_java-version: 21 + + - name: Build Floodgate-Modded + run: ./gradlew build + - name: Publish to Modrinth + if: ${{ success() && github.repository == 'GeyserMC/Floodgate-Modded' && github.ref_name == 'master' }} uses: gradle/gradle-build-action@3bfe3a46584a206fb8361cdedd0647b0c4204232 env: MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} with: arguments: modrinth gradle-home-cache-cleanup: true + - name: Archive Artifacts - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 + uses: GeyserMC/actions/upload-multi-artifact@master if: success() with: - name: Floodgate Fabric - path: build/libs/floodgate-fabric.jar - if-no-files-found: error \ No newline at end of file + artifacts: | + Floodgate-Fabric:fabric/build/libs/floodgate-fabric.jar + Floodgate-NeoForge:neoforge/build/libs/floodgate-neoforge.jar diff --git a/README.md b/README.md index 15381f4..a1a7300 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,11 @@ -# Floodgate-Fabric -Fabric port of the hybrid mode plugin to allow for connections from Geyser to join online mode servers. +# Floodgate-Modded +Fabric and NeoForge ports of the hybrid mode plugin to allow for connections from Geyser to join online mode servers. -Download: https://ci.opencollab.dev/job/GeyserMC/job/Floodgate-Fabric/job/master/ +Hybrid mode mod to allow for connections from Geyser to join online mode servers. +Geyser is an open collaboration project by CubeCraft Games. + +See the Floodgate section in the GeyserMC Wiki for more info about what Floodgate is, how you setup Floodgate and known issues/caveats. +Additionally, it includes a more in-depth look into how Floodgate works and the Floodgate API. + +Wiki: https://wiki.geysermc.org/floodgate/ +Download: https://modrinth.com/mod/floodgate diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts new file mode 100644 index 0000000..4790079 --- /dev/null +++ b/build-logic/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + `kotlin-dsl` +} + +repositories { + gradlePluginPortal() + mavenCentral() + maven("https://maven.architectury.dev/") + maven("https://maven.fabricmc.net/") + maven("https://maven.neoforged.net/releases/") +} + +dependencies { + // Used to access version catalogue from the convention plugins + // this is OK as long as the same version catalog is used in the main build and build-logic + // see https://github.com/gradle/gradle/issues/15383#issuecomment-779893192 + implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) + implementation(libs.indra) + implementation(libs.shadow) + implementation(libs.architectury.plugin) + implementation(libs.architectury.loom) + implementation(libs.minotaur) +} diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts new file mode 100644 index 0000000..63bde18 --- /dev/null +++ b/build-logic/settings.gradle.kts @@ -0,0 +1,11 @@ +@file:Suppress("UnstableApiUsage") + +dependencyResolutionManagement { + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} + +rootProject.name = "build-logic" \ No newline at end of file diff --git a/build-logic/src/main/kotlin/LibsAccessor.kt b/build-logic/src/main/kotlin/LibsAccessor.kt new file mode 100644 index 0000000..2a0c09e --- /dev/null +++ b/build-logic/src/main/kotlin/LibsAccessor.kt @@ -0,0 +1,6 @@ +import org.gradle.accessors.dm.LibrariesForLibs +import org.gradle.api.Project +import org.gradle.kotlin.dsl.getByType + +val Project.libs: LibrariesForLibs + get() = rootProject.extensions.getByType() \ No newline at end of file diff --git a/build-logic/src/main/kotlin/extensions.kt b/build-logic/src/main/kotlin/extensions.kt new file mode 100644 index 0000000..f4c2269 --- /dev/null +++ b/build-logic/src/main/kotlin/extensions.kt @@ -0,0 +1,36 @@ +import org.gradle.api.Project +import org.gradle.api.artifacts.MinimalExternalModuleDependency +import org.gradle.api.artifacts.ProjectDependency +import org.gradle.api.provider.Provider + +val providedDependencies = mutableMapOf>() + +fun Project.provided(pattern: String, name: String, excludedOn: Int = 0b110) { + providedDependencies.getOrPut(project.name) { mutableSetOf() } + .add("${calcExclusion(pattern, 0b100, excludedOn)}:${calcExclusion(name, 0b10, excludedOn)}") +} + +fun Project.provided(dependency: ProjectDependency) = + provided(dependency.group!!, dependency.name) + +fun Project.provided(dependency: MinimalExternalModuleDependency) = + provided(dependency.module.group, dependency.module.name) + +fun Project.provided(provider: Provider) = + provided(provider.get()) + +fun getProvidedDependenciesForProject(projectName: String): MutableSet { + return providedDependencies.getOrDefault(projectName, emptySet()).toMutableSet() +} + +private fun calcExclusion(section: String, bit: Int, excludedOn: Int): String = + if (excludedOn and bit > 0) section else "" + +fun projectVersion(project: Project): String = + project.version.toString().replace("SNAPSHOT", "b" + buildNumber()) + +fun versionName(project: Project): String = + "Floodgate-" + project.name.replaceFirstChar { it.uppercase() } + "-" + projectVersion(project) + +fun buildNumber(): Int = + (System.getenv("GITHUB_RUN_NUMBER"))?.let { Integer.parseInt(it) } ?: -1 diff --git a/build-logic/src/main/kotlin/floodgate-modded.base-conventions.gradle.kts b/build-logic/src/main/kotlin/floodgate-modded.base-conventions.gradle.kts new file mode 100644 index 0000000..77ef1e8 --- /dev/null +++ b/build-logic/src/main/kotlin/floodgate-modded.base-conventions.gradle.kts @@ -0,0 +1,37 @@ +plugins { + `java-library` + id("net.kyori.indra") +} + +dependencies { + compileOnly("org.checkerframework", "checker-qual", "3.19.0") +} + +indra { + github("GeyserMC", "floodgate-modded") { + ci(true) + issues(true) + scm(true) + } + mitLicense() + + javaVersions { + target(21) + } +} + +tasks { + processResources { + filesMatching(listOf("fabric.mod.json", "META-INF/neoforge.mods.toml")) { + expand( + "id" to "floodgate", + "name" to "Floodgate", + "version" to project.version, + "description" to project.description, + "url" to "https://geysermc.org", + "author" to "GeyserMC", + "minecraft_version" to libs.versions.minecraft.version.get() + ) + } + } +} \ No newline at end of file diff --git a/build-logic/src/main/kotlin/floodgate-modded.build-logic.gradle.kts b/build-logic/src/main/kotlin/floodgate-modded.build-logic.gradle.kts new file mode 100644 index 0000000..ff9b567 --- /dev/null +++ b/build-logic/src/main/kotlin/floodgate-modded.build-logic.gradle.kts @@ -0,0 +1,14 @@ +repositories { + // mavenLocal() + mavenCentral() + maven("https://maven.fabricmc.net/") + maven("https://maven.neoforged.net/releases") + maven("https://repo.opencollab.dev/main/") + maven("https://jitpack.io") { + content { + includeGroupByRegex("com.github.*") + } + } + maven("https://oss.sonatype.org/content/repositories/snapshots/") + maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") +} \ No newline at end of file diff --git a/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts b/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts new file mode 100644 index 0000000..71b70a2 --- /dev/null +++ b/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts @@ -0,0 +1,134 @@ +import net.fabricmc.loom.task.RemapJarTask + +plugins { + id("floodgate-modded.publish-conventions") + id("architectury-plugin") + id("dev.architectury.loom") + id("com.modrinth.minotaur") +} + +// These are all provided by Minecraft/server platforms +provided("com.google.code.gson", "gson") +provided("org.slf4j", ".*") +provided("com.google.guava", "guava") +provided("org.ow2.asm", "asm") +provided("com.nukkitx.fastutil", ".*") + +// these we just don't want to include +provided("org.checkerframework", ".*") +provided("com.google.errorprone", ".*") +provided("com.github.spotbugs", "spotbugs-annotations") +provided("com.google.code.findbugs", ".*") + +// cloud-fabric/cloud-neoforge jij's all cloud depends already +provided("org.incendo", ".*") +provided("io.leangen.geantyref", "geantyref") + +architectury { + minecraft = libs.versions.minecraft.version.get() +} + +loom { + silentMojangMappingsLicense() +} + +configurations { + create("includeTransitive").isTransitive = true +} + +dependencies { + minecraft(libs.minecraft) + mappings(loom.officialMojangMappings()) + + // These are under our own namespace + shadow(libs.floodgate.api) { isTransitive = false } + shadow(libs.floodgate.core) { isTransitive = false } + + // Requires relocation + shadow(libs.bstats) { isTransitive = false } + + // Shadow & relocate these since the (indirectly) depend on quite old dependencies + shadow(libs.guice) { isTransitive = false } + shadow(libs.configutils) { + exclude("org.checkerframework") + exclude("com.google.errorprone") + exclude("com.github.spotbugs") + exclude("com.nukkitx.fastutil") + } + +} + +tasks { + sourcesJar { + archiveClassifier.set("sources") + from(sourceSets.main.get().allSource) + } + + shadowJar { + // Mirrors the example fabric project, otherwise tons of dependencies are shaded that shouldn't be + configurations = listOf(project.configurations.shadow.get()) + + // Relocate these + relocate("org.bstats", "org.geysermc.floodgate.shadow.bstats") + relocate("com.google.inject", "org.geysermc.floodgate.shadow.google.inject") + relocate("org.yaml", "org.geysermc.floodgate.shadow.org.yaml") + + // The remapped shadowJar is the final desired mod jar + archiveVersion.set(project.version.toString()) + archiveClassifier.set("shaded") + } + + remapJar { + dependsOn(shadowJar) + inputFile.set(shadowJar.get().archiveFile) + archiveClassifier.set("") + archiveVersion.set("") + } + + register("remapModrinthJar", RemapJarTask::class) { + dependsOn(shadowJar) + inputFile.set(shadowJar.get().archiveFile) + archiveVersion.set(versionName(project)) + archiveClassifier.set("") + } + + // Readme sync + modrinth.get().dependsOn(tasks.modrinthSyncBody) +} + +afterEvaluate { + val providedDependencies = getProvidedDependenciesForProject(project.name) + + // These are shaded, no need to JiJ them + configurations["shadow"].resolvedConfiguration.resolvedArtifacts.forEach {shadowed -> + val string = "${shadowed.moduleVersion.id.group}:${shadowed.moduleVersion.id.name}" + println("Not including shadowed dependency: $string") + providedDependencies.add(string) + } + + configurations["includeTransitive"].resolvedConfiguration.resolvedArtifacts.forEach { dep -> + if (!providedDependencies.contains("${dep.moduleVersion.id.group}:${dep.moduleVersion.id.name}") + and !providedDependencies.contains("${dep.moduleVersion.id.group}:.*")) { + println("Including dependency via JiJ: ${dep.id}") + dependencies.add("include", dep.moduleVersion.id.toString()) + } else { + println("Not including ${dep.id} for ${project.name}!") + } + } +} + +modrinth { + token.set(System.getenv("MODRINTH_TOKEN")) // Even though this is the default value, apparently this prevents GitHub Actions caching the token? + projectId.set("bWrNNfkb") + versionName.set(versionName(project)) + versionNumber.set(projectVersion(project)) + versionType.set("release") + changelog.set("A changelog can be found at https://github.com/GeyserMC/Floodgate-Modded/commits") + + syncBodyFrom.set(rootProject.file("README.md").readText()) + + uploadFile.set(tasks.getByPath("remapModrinthJar")) + gameVersions.add(libs.minecraft.get().version as String) + gameVersions.add("1.21.1") + failSilently.set(false) +} diff --git a/build-logic/src/main/kotlin/floodgate-modded.publish-conventions.gradle.kts b/build-logic/src/main/kotlin/floodgate-modded.publish-conventions.gradle.kts new file mode 100644 index 0000000..fdfa4ef --- /dev/null +++ b/build-logic/src/main/kotlin/floodgate-modded.publish-conventions.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("floodgate-modded.shadow-conventions") + id("net.kyori.indra.publishing") +} + +indra { + publishSnapshotsTo("geysermc", "https://repo.opencollab.dev/maven-snapshots") + publishReleasesTo("geysermc", "https://repo.opencollab.dev/maven-releases") +} + +publishing { + // skip shadow jar from publishing. Workaround for https://github.com/johnrengelman/shadow/issues/651 + val javaComponent = project.components["java"] as AdhocComponentWithVariants + javaComponent.withVariantsFromConfiguration(configurations["shadowRuntimeElements"]) { skip() } +} \ No newline at end of file diff --git a/build-logic/src/main/kotlin/floodgate-modded.shadow-conventions.gradle.kts b/build-logic/src/main/kotlin/floodgate-modded.shadow-conventions.gradle.kts new file mode 100644 index 0000000..e4affd7 --- /dev/null +++ b/build-logic/src/main/kotlin/floodgate-modded.shadow-conventions.gradle.kts @@ -0,0 +1,37 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + +plugins { + id("floodgate-modded.base-conventions") + id("com.github.johnrengelman.shadow") +} + +tasks { + named("jar") { + archiveClassifier.set("unshaded") + from(project.rootProject.file("LICENSE")) + } + val shadowJar = named("shadowJar") { + archiveBaseName.set(project.name) + archiveVersion.set("") + archiveClassifier.set("") + + val sJar: ShadowJar = this + + doFirst { + providedDependencies[project.name]?.forEach { string -> + sJar.dependencies { + println("Excluding $string from ${project.name}") + exclude(dependency(string)) + } + } + + sJar.dependencies { + exclude(dependency("org.checkerframework:checker-qual:.*")) + exclude(dependency("org.jetbrains:annotations:.*")) + } + } + } + named("build") { + dependsOn(shadowJar) + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 525d512..d81fbb1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,147 +1,24 @@ -import net.fabricmc.loom.task.RemapJarTask - plugins { - id("com.github.johnrengelman.shadow") version "8.1.1" - id("fabric-loom") version "1.6-SNAPSHOT" - id("java") - id("maven-publish") - id("com.modrinth.minotaur") version "2.+" -} - -loom { - accessWidenerPath = file("src/main/resources/floodgate.accesswidener") -} - -dependencies { - //to change the versions see the gradle.properties file - minecraft("com.mojang:minecraft:1.21") - mappings(loom.officialMojangMappings()) - modImplementation("net.fabricmc:fabric-loader:0.15.11") - - // Fabric API. This is technically optional, but you probably want it anyway. - modImplementation("net.fabricmc.fabric-api:fabric-api:0.100.1+1.21") - - // Base Floodgate - implementation("org.geysermc.floodgate:core:2.2.3-20240508.151752-4") - shadow("org.geysermc.floodgate:core:2.2.3-20240508.151752-4") { isTransitive = false } - shadow("org.geysermc.floodgate:api:2.2.3-20240508.151752-4") { isTransitive = false } - - // Requires relocation - shadow("org.bstats:bstats-base:3.0.2") - - // Shadow & relocate these since the (indirectly) depend on quite old dependencies - shadow("com.google.inject:guice:6.0.0") { isTransitive = false } - shadow("org.geysermc.configutils:configutils:1.0-SNAPSHOT") { - exclude("org.checkerframework") - exclude("com.google.errorprone") - exclude("com.github.spotbugs") - exclude("com.nukkitx.fastutil") - } - - include("aopalliance:aopalliance:1.0") - include("javax.inject:javax.inject:1") - include("jakarta.inject:jakarta.inject-api:2.0.1") - include("org.java-websocket:Java-WebSocket:1.5.2") - - // Just like Geyser, include these - include("org.geysermc.geyser", "common", "2.2.3-SNAPSHOT") - include("org.geysermc.cumulus", "cumulus", "1.1.2") - include("org.geysermc.event", "events", "1.1-SNAPSHOT") - include("org.lanternpowered", "lmbda", "2.0.0") // used in events - - // Geyser dependency for the fun injector mixin :))) - modCompileOnly("org.geysermc.geyser:fabric:2.2.3-SNAPSHOT") { isTransitive = false } - - // cloud - include("org.incendo:cloud-fabric:2.0.0-SNAPSHOT") - modImplementation("org.incendo:cloud-fabric:2.0.0-SNAPSHOT") - - // Lombok - compileOnly("org.projectlombok:lombok:1.18.32") - annotationProcessor("org.projectlombok:lombok:1.18.32") -} - -repositories { - // mavenLocal() - mavenCentral() - maven("https://maven.fabricmc.net/") - maven("https://repo.opencollab.dev/main/") - maven("https://jitpack.io") { - content { - includeGroupByRegex("com.github.*") - } - } - maven("https://oss.sonatype.org/content/repositories/snapshots/") - maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") -} - -java { - withSourcesJar() + `java-library` + id("floodgate-modded.build-logic") + alias(libs.plugins.lombok) apply false } -tasks { - shadowJar { - configurations = listOf(project.configurations.shadow.get()) - - relocate("org.bstats", "org.geysermc.floodgate.shadow.bstats") - relocate("com.google.inject", "org.geysermc.floodgate.shadow.google.inject") - relocate("org.yaml", "org.geysermc.floodgate.shadow.org.yaml") - } - - processResources { - filesMatching("fabric.mod.json") { - expand("version" to project.version) - } - } - - remapJar { - dependsOn(shadowJar) - mustRunAfter(shadowJar) - inputFile.set(shadowJar.get().archiveFile) - addNestedDependencies = true // todo? - archiveFileName.set("floodgate-fabric.jar") - } +val platforms = setOf( + projects.fabric, + projects.neoforge, + projects.mod +).map { it.dependencyProject } - register("remapModrinthJar", RemapJarTask::class) { - dependsOn(shadowJar) - inputFile.set(shadowJar.get().archiveFile) - addNestedDependencies = true - archiveVersion.set(project.version.toString() + "+build." + System.getenv("GITHUB_RUN_NUMBER")) - archiveClassifier.set("") +subprojects { + apply { + plugin("java-library") + plugin("io.freefair.lombok") + plugin("floodgate-modded.build-logic") } -} -publishing { - publications { - register("publish", MavenPublication::class) { - from(project.components["java"]) - - // skip shadow jar from publishing. Workaround for https://github.com/johnrengelman/shadow/issues/651 - val javaComponent = project.components["java"] as AdhocComponentWithVariants - javaComponent.withVariantsFromConfiguration(configurations["shadowRuntimeElements"]) { skip() } - } + when (this) { + in platforms -> plugins.apply("floodgate-modded.platform-conventions") + else -> plugins.apply("floodgate-modded.base-conventions") } - - repositories { - mavenLocal() - } -} - -modrinth { - token.set(System.getenv("MODRINTH_TOKEN")) // Prevent GitHub Actions from caching empty Modrinth token - projectId.set("bWrNNfkb") - versionNumber.set(project.version as String + "-" + System.getenv("GITHUB_RUN_NUMBER")) - versionType.set("beta") - changelog.set("A changelog can be found at https://github.com/GeyserMC/Floodgate-Fabric/commits") - - syncBodyFrom.set(rootProject.file("README.md").readText()) - - uploadFile.set(tasks.named("remapModrinthJar")) - gameVersions.addAll("1.21") - - loaders.add("fabric") - - dependencies { - required.project("fabric-api") - } -} +} \ No newline at end of file diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts new file mode 100644 index 0000000..7aef15a --- /dev/null +++ b/fabric/build.gradle.kts @@ -0,0 +1,46 @@ +architectury { + platformSetupLoomIde() + fabric() +} + +// Used to extend runtime/compile classpaths +val common: Configuration by configurations.creating +// Needed to read mixin config in the runServer task, and for the architectury transformer +// (e.g. the @ExpectPlatform annotation) +val developmentFabric: Configuration = configurations.getByName("developmentFabric") +// Our custom transitive include configuration +val includeTransitive: Configuration = configurations.getByName("includeTransitive") + +configurations { + compileClasspath.get().extendsFrom(configurations["common"]) + runtimeClasspath.get().extendsFrom(configurations["common"]) + developmentFabric.extendsFrom(configurations["common"]) +} + +dependencies { + modImplementation(libs.fabric.loader) + modApi(libs.fabric.api) + // "namedElements" configuration should be used to depend on different loom projects + common(project(":mod", configuration = "namedElements")) { isTransitive = false } + // Bundle transformed classes of the common module for production mod jar + shadow(project(path = ":mod", configuration = "transformProductionFabric")) { + isTransitive = false + } + + includeTransitive(libs.floodgate.core) + implementation(libs.floodgate.core) + implementation(libs.guice) + + modImplementation(libs.cloud.fabric) + include(libs.cloud.fabric) +} + +tasks { + remapJar { + archiveBaseName.set("floodgate-fabric") + } + + modrinth { + loaders.add("fabric") + } +} diff --git a/fabric/gradle.properties b/fabric/gradle.properties new file mode 100644 index 0000000..90ee7a2 --- /dev/null +++ b/fabric/gradle.properties @@ -0,0 +1 @@ +loom.platform=fabric \ No newline at end of file diff --git a/fabric/src/main/java/org/geysermc/floodgate/mod/util/fabric/MixinConfigPluginImpl.java b/fabric/src/main/java/org/geysermc/floodgate/mod/util/fabric/MixinConfigPluginImpl.java new file mode 100644 index 0000000..bf8f6d2 --- /dev/null +++ b/fabric/src/main/java/org/geysermc/floodgate/mod/util/fabric/MixinConfigPluginImpl.java @@ -0,0 +1,14 @@ +package org.geysermc.floodgate.mod.util.fabric; + +import net.fabricmc.loader.api.FabricLoader; + +public class MixinConfigPluginImpl { + + public static boolean isGeyserLoaded() { + return FabricLoader.getInstance().isModLoaded("geyser-fabric"); + } + + public static boolean applyProxyFix() { + return FabricLoader.getInstance().isModLoaded("fabricproxy-lite"); + } +} diff --git a/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/FabricFloodgateMod.java b/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/FabricFloodgateMod.java new file mode 100644 index 0000000..ed1a98c --- /dev/null +++ b/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/FabricFloodgateMod.java @@ -0,0 +1,54 @@ +package org.geysermc.floodgate.platform.fabric; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.floodgate.core.module.PluginMessageModule; +import org.geysermc.floodgate.core.module.ServerCommonModule; +import org.geysermc.floodgate.mod.FloodgateMod; +import org.geysermc.floodgate.mod.util.ModTemplateReader; +import org.geysermc.floodgate.platform.fabric.module.FabricCommandModule; +import org.geysermc.floodgate.platform.fabric.module.FabricPlatformModule; + +import java.nio.file.Path; + +public final class FabricFloodgateMod extends FloodgateMod implements ModInitializer { + + private ModContainer container; + + @Override + public void onInitialize() { + container = FabricLoader.getInstance().getModContainer("floodgate").orElseThrow(); + init( + new ServerCommonModule( + FabricLoader.getInstance().getConfigDir().resolve("floodgate"), + new ModTemplateReader() + ), + new FabricPlatformModule(), + new FabricCommandModule(), + new PluginMessageModule() + ); + + ServerLifecycleEvents.SERVER_STARTED.register(this::enable); + + if (isClient()) { + ClientLifecycleEvents.CLIENT_STOPPING.register($ -> this.disable()); + } else { + ServerLifecycleEvents.SERVER_STOPPING.register($ -> this.disable()); + } + } + + @Override + public @Nullable Path resourcePath(String file) { + return container.findPath(file).orElse(null); + } + + @Override + public boolean isClient() { + return FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT; + } +} diff --git a/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/listener/FabricEventRegistration.java b/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/listener/FabricEventRegistration.java new file mode 100644 index 0000000..80e486d --- /dev/null +++ b/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/listener/FabricEventRegistration.java @@ -0,0 +1,14 @@ +package org.geysermc.floodgate.platform.fabric.listener; + +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import org.geysermc.floodgate.core.platform.listener.ListenerRegistration; +import org.geysermc.floodgate.mod.listener.ModEventListener; + +public final class FabricEventRegistration implements ListenerRegistration { + @Override + public void register(ModEventListener listener) { + ServerPlayConnectionEvents.JOIN.register( + (handler, sender, server) -> listener.onPlayerJoin(handler.getPlayer().getUUID()) + ); + } +} diff --git a/src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java b/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/module/FabricCommandModule.java similarity index 64% rename from src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java rename to fabric/src/main/java/org/geysermc/floodgate/platform/fabric/module/FabricCommandModule.java index c02adb1..4d9ccb8 100644 --- a/src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java +++ b/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/module/FabricCommandModule.java @@ -1,13 +1,15 @@ -package org.geysermc.floodgate.module; +package org.geysermc.floodgate.platform.fabric.module; import com.google.inject.Provides; import com.google.inject.Singleton; import lombok.SneakyThrows; import net.minecraft.commands.CommandSourceStack; -import org.geysermc.floodgate.platform.command.CommandUtil; -import org.geysermc.floodgate.player.FloodgateCommandPreprocessor; -import org.geysermc.floodgate.player.UserAudience; -import org.geysermc.floodgate.player.audience.FloodgateSenderMapper; +import org.geysermc.floodgate.core.module.CommandModule; +import org.geysermc.floodgate.core.platform.command.CommandUtil; +import org.geysermc.floodgate.core.player.FloodgateCommandPreprocessor; +import org.geysermc.floodgate.core.player.UserAudience; +import org.geysermc.floodgate.core.player.audience.FloodgateSenderMapper; +import org.geysermc.floodgate.mod.util.ModCommandUtil; import org.incendo.cloud.CommandManager; import org.incendo.cloud.execution.ExecutionCoordinator; import org.incendo.cloud.fabric.FabricCommandManager; @@ -23,6 +25,7 @@ public CommandManager commandManager(CommandUtil commandUtil) { new FloodgateSenderMapper<>(commandUtil) ); commandManager.registerCommandPreProcessor(new FloodgateCommandPreprocessor<>(commandUtil)); + ((ModCommandUtil) commandUtil).setCommandManager(commandManager); return commandManager; } diff --git a/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/module/FabricPlatformModule.java b/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/module/FabricPlatformModule.java new file mode 100644 index 0000000..2f4cf18 --- /dev/null +++ b/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/module/FabricPlatformModule.java @@ -0,0 +1,40 @@ +package org.geysermc.floodgate.platform.fabric.module; + +import com.google.inject.Provides; +import com.google.inject.Singleton; +import com.google.inject.name.Named; +import org.geysermc.floodgate.core.platform.listener.ListenerRegistration; +import org.geysermc.floodgate.core.platform.pluginmessage.PluginMessageUtils; +import org.geysermc.floodgate.core.pluginmessage.PluginMessageRegistration; +import org.geysermc.floodgate.mod.listener.ModEventListener; +import org.geysermc.floodgate.mod.module.ModPlatformModule; +import org.geysermc.floodgate.platform.fabric.listener.FabricEventRegistration; +import org.geysermc.floodgate.platform.fabric.pluginmessage.FabricPluginMessageRegistration; +import org.geysermc.floodgate.platform.fabric.pluginmessage.FabricPluginMessageUtils; + +public class FabricPlatformModule extends ModPlatformModule { + + @Provides + @Singleton + public ListenerRegistration listenerRegistration() { + return new FabricEventRegistration(); + } + + @Provides + @Singleton + public PluginMessageUtils pluginMessageUtils() { + return new FabricPluginMessageUtils(); + } + + @Provides + @Singleton + public PluginMessageRegistration pluginMessageRegister() { + return new FabricPluginMessageRegistration(); + } + + @Provides + @Named("implementationName") + public String implementationName() { + return "Fabric"; + } +} diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageRegistration.java b/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/pluginmessage/FabricPluginMessageRegistration.java similarity index 85% rename from src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageRegistration.java rename to fabric/src/main/java/org/geysermc/floodgate/platform/fabric/pluginmessage/FabricPluginMessageRegistration.java index ee5b781..d9da35c 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageRegistration.java +++ b/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/pluginmessage/FabricPluginMessageRegistration.java @@ -1,11 +1,13 @@ -package org.geysermc.floodgate.pluginmessage; +package org.geysermc.floodgate.platform.fabric.pluginmessage; import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; -import org.geysermc.floodgate.pluginmessage.payloads.FormPayload; -import org.geysermc.floodgate.pluginmessage.payloads.PacketPayload; -import org.geysermc.floodgate.pluginmessage.payloads.SkinPayload; -import org.geysermc.floodgate.pluginmessage.payloads.TransferPayload; +import org.geysermc.floodgate.core.pluginmessage.PluginMessageChannel; +import org.geysermc.floodgate.core.pluginmessage.PluginMessageRegistration; +import org.geysermc.floodgate.mod.pluginmessage.payloads.FormPayload; +import org.geysermc.floodgate.mod.pluginmessage.payloads.PacketPayload; +import org.geysermc.floodgate.mod.pluginmessage.payloads.SkinPayload; +import org.geysermc.floodgate.mod.pluginmessage.payloads.TransferPayload; public class FabricPluginMessageRegistration implements PluginMessageRegistration { @Override diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageUtils.java b/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/pluginmessage/FabricPluginMessageUtils.java similarity index 61% rename from src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageUtils.java rename to fabric/src/main/java/org/geysermc/floodgate/platform/fabric/pluginmessage/FabricPluginMessageUtils.java index 6a5b3b4..9aa1094 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageUtils.java +++ b/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/pluginmessage/FabricPluginMessageUtils.java @@ -1,21 +1,14 @@ -package org.geysermc.floodgate.pluginmessage; +package org.geysermc.floodgate.platform.fabric.pluginmessage; -import io.netty.buffer.Unpooled; -import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; -import org.geysermc.floodgate.MinecraftServerHolder; -import org.geysermc.floodgate.api.FloodgateApi; -import org.geysermc.floodgate.api.InstanceHolder; -import org.geysermc.floodgate.platform.pluginmessage.PluginMessageUtils; -import org.geysermc.floodgate.pluginmessage.payloads.FormPayload; -import org.geysermc.floodgate.pluginmessage.payloads.PacketPayload; -import org.geysermc.floodgate.pluginmessage.payloads.SkinPayload; -import org.geysermc.floodgate.pluginmessage.payloads.TransferPayload; +import org.geysermc.floodgate.core.platform.pluginmessage.PluginMessageUtils; +import org.geysermc.floodgate.mod.MinecraftServerHolder; +import org.geysermc.floodgate.mod.pluginmessage.payloads.FormPayload; +import org.geysermc.floodgate.mod.pluginmessage.payloads.PacketPayload; +import org.geysermc.floodgate.mod.pluginmessage.payloads.SkinPayload; +import org.geysermc.floodgate.mod.pluginmessage.payloads.TransferPayload; import java.util.Objects; import java.util.UUID; diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..62a1c73 --- /dev/null +++ b/fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,30 @@ +{ + "schemaVersion": 1, + "id": "$id", + "version": "$version", + "name": "$name", + "description": "$description", + "authors": [ + "$author" + ], + "contact": { + "website": "$url", + "repo": "https://github.com/GeyserMC/Floodgate-Modded" + }, + "license": "MIT", + "environment": "*", + "entrypoints": { + "main": [ + "org.geysermc.floodgate.platform.fabric.FabricFloodgateMod" + ] + }, + "accessWidener": "floodgate.accesswidener", + "mixins": [ + "floodgate.mixins.json" + ], + "depends": { + "fabricloader": ">=0.15.10", + "fabric": "*", + "minecraft": ">=$minecraft_version" + } +} diff --git a/gradle.properties b/gradle.properties index 573a53c..a5ca268 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,9 @@ -# Done to increase the memory available to gradle. org.gradle.jvmargs=-Xmx2G +org.gradle.daemon=false +org.gradle.caching=true +org.gradle.vfs.watch=false + # Mod Properties -version=2.2.3-SNAPSHOT +version=2.2.4-SNAPSHOT group=org.geysermc.floodgate -archives_base_name=floodgate-fabric +id=floodgate-modded diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..bae1d82 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,57 @@ +[versions] +geyser = "2.2.3-SNAPSHOT" +blossom = "1.2.0" +indra = "3.1.3" +shadow = "8.1.1" +architectury-plugin = "3.4-SNAPSHOT" +architectury-loom = "1.7-SNAPSHOT" +minecraft-version = "1.21" +minotaur = "2.+" +guice = "6.0.0" +cloud = "2.0.0-beta.7" +lombok = "8.6" +bstats = "3.0.2" +configutils = "1.0-SNAPSHOT" +mixin = "0.8.5" +asm = "5.2" +floodgate = "core-repackage-2.2.3-SNAPSHOT" + +# fabric +fabric-loader = "0.15.11" +fabric-api = "0.100.1+1.21" + +# neoforge +neoforge-version = "21.0.87-beta" + +[libraries] +floodgate-core = { group = "org.geysermc.floodgate", name = "core", version.ref = "floodgate" } +floodgate-api = { group = "org.geysermc.floodgate", name = "api", version.ref = "floodgate" } + +geyser-fabric = { group = "org.geysermc.geyser", name = "fabric", version.ref = "geyser" } +geyser-mod = { group = "org.geysermc.geyser", name = "mod", version.ref = "geyser" } +geyser-core = { group = "org.geysermc.geyser", name = "core", version.ref = "geyser" } +indra = { group = "net.kyori", name = "indra-common", version.ref = "indra" } +shadow = { group = "com.github.johnrengelman", name = "shadow", version.ref = "shadow" } +architectury-plugin = { group = "architectury-plugin", name = "architectury-plugin.gradle.plugin", version.ref = "architectury-plugin" } +architectury-loom = { group = "dev.architectury.loom", name = "dev.architectury.loom.gradle.plugin", version.ref = "architectury-loom" } +guice = { group = "com.google.inject", name = "guice", version.ref = "guice" } +bstats = { group = "org.bstats", name = "bstats-base", version.ref = "bstats" } +configutils = { group = "org.geysermc.configutils", name = "configutils", version.ref = "configutils" } +cloud-fabric = { group = "org.incendo", name = "cloud-fabric", version.ref = "cloud" } +cloud-neoforge = { group = "org.incendo", name = "cloud-neoforge", version.ref = "cloud" } +minotaur = { group = "com.modrinth.minotaur", name = "Minotaur", version.ref = "minotaur" } +mixin = { group = "org.spongepowered", name = "mixin", version.ref = "mixin" } +asm = { group = "org.ow2.asm", name = "asm-debug-all", version.ref = "asm" } +minecraft = { group = "com.mojang", name = "minecraft", version.ref = "minecraft-version" } + +# Fabric +fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version.ref = "fabric-loader" } +fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric-api" } + +# NeoForge +neoforge = { group = "net.neoforged", name = "neoforge", version.ref = "neoforge-version" } + +[plugins] +lombok = { id = "io.freefair.lombok", version.ref = "lombok" } +blossom = { id = "net.kyori.blossom", version.ref = "blossom"} +indra = { id = "net.kyori.indra", version.ref = "indra" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch literal 43453 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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
NnX)EH+Nua)3Y(c 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 48c0a02..a441313 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index c53aefa..1aa94a4 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -32,10 +32,10 @@ # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +80,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +131,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ @@ -205,6 +214,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index ac1b06f..7101f8e 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,13 +41,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/mod/build.gradle.kts b/mod/build.gradle.kts new file mode 100644 index 0000000..3d990ac --- /dev/null +++ b/mod/build.gradle.kts @@ -0,0 +1,33 @@ +architectury { + common("neoforge", "fabric") +} + +loom { + accessWidenerPath = file("src/main/resources/floodgate.accesswidener") + mixin.defaultRefmapName.set("floodgate-refmap.json") +} + +dependencies { + api(libs.floodgate.core) + api(libs.floodgate.api) + api(libs.guice) + + compileOnly(libs.mixin) + compileOnly(libs.asm) + modCompileOnly(libs.geyser.mod) { isTransitive = false } + modCompileOnly(libs.geyser.core) { isTransitive = false } + + // Only here to suppress "unknown enum constant EnvType.CLIENT" warnings. + compileOnly(libs.fabric.loader) +} + +afterEvaluate { + // We don't need these + tasks.named("remapModrinthJar").configure { + enabled = false + } + + tasks.named("modrinth").configure { + enabled = false + } +} \ No newline at end of file diff --git a/mod/src/main/java/org/geysermc/floodgate/mod/FloodgateMod.java b/mod/src/main/java/org/geysermc/floodgate/mod/FloodgateMod.java new file mode 100644 index 0000000..b765b4f --- /dev/null +++ b/mod/src/main/java/org/geysermc/floodgate/mod/FloodgateMod.java @@ -0,0 +1,59 @@ +package org.geysermc.floodgate.mod; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Module; +import net.minecraft.server.MinecraftServer; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.core.FloodgatePlatform; +import org.geysermc.floodgate.mod.module.ModAddonModule; +import org.geysermc.floodgate.mod.module.ModListenerModule; + +import java.nio.file.Path; + +public abstract class FloodgateMod { + public static FloodgateMod INSTANCE; + + private boolean started; + private FloodgatePlatform platform; + protected Injector injector; + + protected void init(Module... modules) { + INSTANCE = this; + injector = Guice.createInjector(modules); + platform = injector.getInstance(FloodgatePlatform.class); + } + + protected void enable(MinecraftServer server) { + long ctm = System.currentTimeMillis(); + + // Stupid hack, see the class for more information + // This can probably be Guice-i-fied but that is beyond me + MinecraftServerHolder.set(server); + + if (!started) { + platform.enable( + new ModAddonModule(), + new ModListenerModule() + ); + started = true; + } + + long endCtm = System.currentTimeMillis(); + injector.getInstance(FloodgateLogger.class) + .translatedInfo("floodgate.core.finish", endCtm - ctm); + } + + protected void disable() { + platform.disable(); + } + + protected void enable(Module... module) { + platform.enable(module); + } + + public @Nullable abstract Path resourcePath(String file); + + public abstract boolean isClient(); +} diff --git a/src/main/java/org/geysermc/floodgate/MinecraftServerHolder.java b/mod/src/main/java/org/geysermc/floodgate/mod/MinecraftServerHolder.java similarity index 92% rename from src/main/java/org/geysermc/floodgate/MinecraftServerHolder.java rename to mod/src/main/java/org/geysermc/floodgate/mod/MinecraftServerHolder.java index b1f0c9e..85be6d2 100644 --- a/src/main/java/org/geysermc/floodgate/MinecraftServerHolder.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/MinecraftServerHolder.java @@ -1,4 +1,4 @@ -package org.geysermc.floodgate; +package org.geysermc.floodgate.mod; import net.minecraft.server.MinecraftServer; diff --git a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java b/mod/src/main/java/org/geysermc/floodgate/mod/data/ModDataAddon.java similarity index 78% rename from src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java rename to mod/src/main/java/org/geysermc/floodgate/mod/data/ModDataAddon.java index ca93aae..6f0bb8e 100644 --- a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/data/ModDataAddon.java @@ -1,18 +1,18 @@ -package org.geysermc.floodgate.addon.data; +package org.geysermc.floodgate.mod.data; import com.google.inject.Inject; import com.google.inject.name.Named; import io.netty.channel.Channel; import io.netty.util.AttributeKey; -import org.geysermc.floodgate.api.SimpleFloodgateApi; import org.geysermc.floodgate.api.inject.InjectorAddon; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.player.FloodgatePlayer; -import org.geysermc.floodgate.config.FloodgateConfig; -import org.geysermc.floodgate.player.FloodgateHandshakeHandler; -import org.geysermc.floodgate.util.Utils; +import org.geysermc.floodgate.core.api.SimpleFloodgateApi; +import org.geysermc.floodgate.core.config.FloodgateConfig; +import org.geysermc.floodgate.core.player.FloodgateHandshakeHandler; +import org.geysermc.floodgate.core.util.Utils; -public final class FabricDataAddon implements InjectorAddon { +public final class ModDataAddon implements InjectorAddon { @Inject private FloodgateHandshakeHandler handshakeHandler; @Inject private FloodgateConfig config; @Inject private SimpleFloodgateApi api; @@ -34,7 +34,7 @@ public final class FabricDataAddon implements InjectorAddon { public void onInject(Channel channel, boolean toServer) { channel.pipeline().addBefore( packetHandlerName, "floodgate_data_handler", - new FabricDataHandler(handshakeHandler, config, kickMessageAttribute, logger) + new ModDataHandler(handshakeHandler, config, kickMessageAttribute, logger) ); } diff --git a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java b/mod/src/main/java/org/geysermc/floodgate/mod/data/ModDataHandler.java similarity index 89% rename from src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java rename to mod/src/main/java/org/geysermc/floodgate/mod/data/ModDataHandler.java index eaced77..30590df 100644 --- a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/data/ModDataHandler.java @@ -1,8 +1,10 @@ -package org.geysermc.floodgate.addon.data; +package org.geysermc.floodgate.mod.data; +import com.mojang.authlib.GameProfile; import com.mojang.authlib.minecraft.MinecraftSessionService; import com.mojang.logging.LogUtils; import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; import io.netty.util.AttributeKey; import net.minecraft.DefaultUncaughtExceptionHandler; import net.minecraft.network.Connection; @@ -10,28 +12,28 @@ import net.minecraft.network.protocol.handshake.ClientIntentionPacket; import net.minecraft.network.protocol.login.ServerboundHelloPacket; import net.minecraft.server.network.ServerLoginPacketListenerImpl; -import org.geysermc.floodgate.MinecraftServerHolder; import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.mixin.ConnectionMixin; -import org.geysermc.floodgate.mixin.ClientIntentionPacketMixinInterface; -import com.mojang.authlib.GameProfile; -import io.netty.channel.ChannelHandlerContext; import org.geysermc.floodgate.api.player.FloodgatePlayer; -import org.geysermc.floodgate.config.FloodgateConfig; -import org.geysermc.floodgate.player.FloodgateHandshakeHandler; -import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult; +import org.geysermc.floodgate.core.addon.data.CommonDataHandler; +import org.geysermc.floodgate.core.addon.data.PacketBlocker; +import org.geysermc.floodgate.core.config.FloodgateConfig; +import org.geysermc.floodgate.core.player.FloodgateHandshakeHandler; +import org.geysermc.floodgate.core.player.FloodgateHandshakeHandler.HandshakeResult; +import org.geysermc.floodgate.mod.MinecraftServerHolder; +import org.geysermc.floodgate.mod.mixin.ClientIntentionPacketMixinInterface; +import org.geysermc.floodgate.mod.mixin.ConnectionMixin; import org.slf4j.Logger; import java.net.InetSocketAddress; -public final class FabricDataHandler extends CommonDataHandler { +public final class ModDataHandler extends CommonDataHandler { private static final Logger LOGGER = LogUtils.getLogger(); private final FloodgateLogger logger; private Connection networkManager; private FloodgatePlayer player; - public FabricDataHandler( + public ModDataHandler( FloodgateHandshakeHandler handshakeHandler, FloodgateConfig config, AttributeKey kickMessageAttribute, FloodgateLogger logger) { diff --git a/src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java b/mod/src/main/java/org/geysermc/floodgate/mod/inject/ModInjector.java similarity index 78% rename from src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java rename to mod/src/main/java/org/geysermc/floodgate/mod/inject/ModInjector.java index 15f0a1a..dcd2d23 100644 --- a/src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/inject/ModInjector.java @@ -1,19 +1,21 @@ -package org.geysermc.floodgate.inject.fabric; +package org.geysermc.floodgate.mod.inject; import com.google.inject.Inject; -import io.netty.channel.*; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; import lombok.Getter; import lombok.RequiredArgsConstructor; -import lombok.Setter; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.inject.CommonPlatformInjector; +import org.geysermc.floodgate.core.inject.CommonPlatformInjector; @RequiredArgsConstructor -public final class FabricInjector extends CommonPlatformInjector { +public final class ModInjector extends CommonPlatformInjector { - @Setter @Getter - private static FabricInjector instance; + public static ModInjector INSTANCE = new ModInjector(); @Getter private final boolean injected = true; diff --git a/src/main/java/org/geysermc/floodgate/listener/FabricEventListener.java b/mod/src/main/java/org/geysermc/floodgate/mod/listener/ModEventListener.java similarity index 55% rename from src/main/java/org/geysermc/floodgate/listener/FabricEventListener.java rename to mod/src/main/java/org/geysermc/floodgate/mod/listener/ModEventListener.java index b94e1d3..6f116e3 100644 --- a/src/main/java/org/geysermc/floodgate/listener/FabricEventListener.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/listener/ModEventListener.java @@ -1,21 +1,20 @@ -package org.geysermc.floodgate.listener; +package org.geysermc.floodgate.mod.listener; import com.google.inject.Inject; -import net.fabricmc.fabric.api.networking.v1.PacketSender; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.network.ServerGamePacketListenerImpl; import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.player.FloodgatePlayer; -import org.geysermc.floodgate.util.LanguageManager; +import org.geysermc.floodgate.core.util.LanguageManager; -public final class FabricEventListener { +import java.util.UUID; + +public final class ModEventListener { @Inject private FloodgateApi api; @Inject private FloodgateLogger logger; @Inject private LanguageManager languageManager; - public void onPlayerJoin(ServerGamePacketListenerImpl networkHandler, PacketSender packetSender, MinecraftServer server) { - FloodgatePlayer player = api.getPlayer(networkHandler.player.getUUID()); + public void onPlayerJoin(UUID uuid) { + FloodgatePlayer player = api.getPlayer(uuid); if (player != null) { logger.translatedInfo( "floodgate.ingame.login_name", diff --git a/src/main/java/org/geysermc/floodgate/logger/Log4jFloodgateLogger.java b/mod/src/main/java/org/geysermc/floodgate/mod/logger/Log4jFloodgateLogger.java similarity index 88% rename from src/main/java/org/geysermc/floodgate/logger/Log4jFloodgateLogger.java rename to mod/src/main/java/org/geysermc/floodgate/mod/logger/Log4jFloodgateLogger.java index d24223f..b3eb481 100644 --- a/src/main/java/org/geysermc/floodgate/logger/Log4jFloodgateLogger.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/logger/Log4jFloodgateLogger.java @@ -1,4 +1,4 @@ -package org.geysermc.floodgate.logger; +package org.geysermc.floodgate.mod.logger; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -7,10 +7,10 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.config.Configurator; import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.config.FloodgateConfig; -import org.geysermc.floodgate.util.LanguageManager; +import org.geysermc.floodgate.core.config.FloodgateConfig; +import org.geysermc.floodgate.core.util.LanguageManager; -import static org.geysermc.floodgate.util.MessageFormatter.format; +import static org.geysermc.floodgate.core.util.MessageFormatter.format; @Singleton public final class Log4jFloodgateLogger implements FloodgateLogger { diff --git a/src/main/java/org/geysermc/floodgate/mixin/ChunkMapMixin.java b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/ChunkMapMixin.java similarity index 88% rename from src/main/java/org/geysermc/floodgate/mixin/ChunkMapMixin.java rename to mod/src/main/java/org/geysermc/floodgate/mod/mixin/ChunkMapMixin.java index 3805dbb..c959ebf 100644 --- a/src/main/java/org/geysermc/floodgate/mixin/ChunkMapMixin.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/ChunkMapMixin.java @@ -1,4 +1,4 @@ -package org.geysermc.floodgate.mixin; +package org.geysermc.floodgate.mod.mixin; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import net.minecraft.server.level.ChunkMap; diff --git a/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixin.java b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/ClientIntentionPacketMixin.java similarity index 92% rename from src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixin.java rename to mod/src/main/java/org/geysermc/floodgate/mod/mixin/ClientIntentionPacketMixin.java index e8c0dbf..75b5df4 100644 --- a/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixin.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/ClientIntentionPacketMixin.java @@ -1,4 +1,4 @@ -package org.geysermc.floodgate.mixin; +package org.geysermc.floodgate.mod.mixin; import net.minecraft.network.protocol.handshake.ClientIntentionPacket; import org.spongepowered.asm.mixin.Mixin; diff --git a/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixinInterface.java b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/ClientIntentionPacketMixinInterface.java similarity index 90% rename from src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixinInterface.java rename to mod/src/main/java/org/geysermc/floodgate/mod/mixin/ClientIntentionPacketMixinInterface.java index 8ffdcba..ee06792 100644 --- a/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixinInterface.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/ClientIntentionPacketMixinInterface.java @@ -1,4 +1,4 @@ -package org.geysermc.floodgate.mixin; +package org.geysermc.floodgate.mod.mixin; import net.minecraft.network.protocol.handshake.ClientIntentionPacket; import org.spongepowered.asm.mixin.Mixin; diff --git a/src/main/java/org/geysermc/floodgate/mixin/ConnectionMixin.java b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/ConnectionMixin.java similarity index 87% rename from src/main/java/org/geysermc/floodgate/mixin/ConnectionMixin.java rename to mod/src/main/java/org/geysermc/floodgate/mod/mixin/ConnectionMixin.java index 6494822..e085cd3 100644 --- a/src/main/java/org/geysermc/floodgate/mixin/ConnectionMixin.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/ConnectionMixin.java @@ -1,4 +1,4 @@ -package org.geysermc.floodgate.mixin; +package org.geysermc.floodgate.mod.mixin; import net.minecraft.network.Connection; import org.spongepowered.asm.mixin.Mixin; diff --git a/mod/src/main/java/org/geysermc/floodgate/mod/mixin/FloodgateUtilMixin.java b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/FloodgateUtilMixin.java new file mode 100644 index 0000000..e654b7c --- /dev/null +++ b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/FloodgateUtilMixin.java @@ -0,0 +1,49 @@ +package org.geysermc.floodgate.mod.mixin; + +import org.geysermc.floodgate.core.util.Utils; +import org.geysermc.floodgate.mod.FloodgateMod; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Mixins into Floodgate's {@link Utils} class to modify how resources are loaded from the jar. + * This must be done due to mod platforms sharing a classloader across mods - this leads to Floodgate + * loading Geyser's language files, as they're not prefixed to avoid conflicts. + * To resolve this, this mixin replaces those calls with the platform-appropriate methods to load files. + */ +@Mixin(value = Utils.class, remap = false) +public class FloodgateUtilMixin { + + @Redirect(method = "readProperties", + at = @At(value = "INVOKE", target = "Ljava/lang/ClassLoader;getResourceAsStream(Ljava/lang/String;)Ljava/io/InputStream;")) + private static InputStream floodgate$redirectInputStream(ClassLoader instance, String string) { + Path path = FloodgateMod.INSTANCE.resourcePath(string); + try { + return path == null ? null : Files.newInputStream(path); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Redirect(method = "getGeneratedClassesForAnnotation(Ljava/lang/String;)Ljava/util/Set;", + at = @At(value = "INVOKE", target = "Ljava/lang/ClassLoader;getResourceAsStream(Ljava/lang/String;)Ljava/io/InputStream;")) + private static InputStream floodgate$redirectInputStreamAnnotation(ClassLoader instance, String string) { + Path path = FloodgateMod.INSTANCE.resourcePath(string); + + if (path == null) { + throw new IllegalStateException("Unable to find annotation class! " + string); + } + + try { + return Files.newInputStream(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/org/geysermc/floodgate/mixin/GeyserModInjectorMixin.java b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/GeyserModInjectorMixin.java similarity index 71% rename from src/main/java/org/geysermc/floodgate/mixin/GeyserModInjectorMixin.java rename to mod/src/main/java/org/geysermc/floodgate/mod/mixin/GeyserModInjectorMixin.java index 4b117ad..39ab73d 100644 --- a/src/main/java/org/geysermc/floodgate/mixin/GeyserModInjectorMixin.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/GeyserModInjectorMixin.java @@ -1,7 +1,7 @@ -package org.geysermc.floodgate.mixin; +package org.geysermc.floodgate.mod.mixin; import io.netty.channel.ChannelFuture; -import org.geysermc.floodgate.inject.fabric.FabricInjector; +import org.geysermc.floodgate.mod.inject.ModInjector; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.platform.mod.GeyserModInjector; import org.spongepowered.asm.mixin.Mixin; @@ -19,7 +19,7 @@ public class GeyserModInjectorMixin { private List allServerChannels; @Inject(method = "initializeLocalChannel0", at = @At(value = "INVOKE_ASSIGN", target = "Ljava/util/List;add(Ljava/lang/Object;)Z")) - public void onChannelAdd(GeyserBootstrap bootstrap, CallbackInfo ci) { - FabricInjector.getInstance().injectClient(this.allServerChannels.get(this.allServerChannels.size() - 1)); + public void floodgate$onChannelAdd(GeyserBootstrap bootstrap, CallbackInfo ci) { + ModInjector.INSTANCE.injectClient(this.allServerChannels.get(this.allServerChannels.size() - 1)); } } diff --git a/src/main/java/org/geysermc/floodgate/mixin/ServerConnectionListenerMixin.java b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/ServerConnectionListenerMixin.java similarity index 73% rename from src/main/java/org/geysermc/floodgate/mixin/ServerConnectionListenerMixin.java rename to mod/src/main/java/org/geysermc/floodgate/mod/mixin/ServerConnectionListenerMixin.java index dd04e51..e094da2 100644 --- a/src/main/java/org/geysermc/floodgate/mixin/ServerConnectionListenerMixin.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/ServerConnectionListenerMixin.java @@ -1,8 +1,8 @@ -package org.geysermc.floodgate.mixin; +package org.geysermc.floodgate.mod.mixin; -import net.minecraft.server.network.ServerConnectionListener; -import org.geysermc.floodgate.inject.fabric.FabricInjector; import io.netty.channel.ChannelFuture; +import net.minecraft.server.network.ServerConnectionListener; +import org.geysermc.floodgate.mod.inject.ModInjector; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -19,7 +19,7 @@ public abstract class ServerConnectionListenerMixin { @Shadow @Final private List channels; @Inject(method = "startTcpServerListener", at = @At(value = "INVOKE_ASSIGN", target = "Ljava/util/List;add(Ljava/lang/Object;)Z")) - public void onChannelAdd(InetAddress address, int port, CallbackInfo ci) { - FabricInjector.getInstance().injectClient(this.channels.get(this.channels.size() - 1)); + public void floodgate$onChannelAdd(InetAddress address, int port, CallbackInfo ci) { + ModInjector.INSTANCE.injectClient(this.channels.get(this.channels.size() - 1)); } } diff --git a/src/main/java/org/geysermc/floodgate/module/FabricAddonModule.java b/mod/src/main/java/org/geysermc/floodgate/mod/module/ModAddonModule.java similarity index 63% rename from src/main/java/org/geysermc/floodgate/module/FabricAddonModule.java rename to mod/src/main/java/org/geysermc/floodgate/mod/module/ModAddonModule.java index 2dd731c..fdd9f57 100644 --- a/src/main/java/org/geysermc/floodgate/module/FabricAddonModule.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/module/ModAddonModule.java @@ -1,15 +1,15 @@ -package org.geysermc.floodgate.module; +package org.geysermc.floodgate.mod.module; -import org.geysermc.floodgate.addon.data.FabricDataAddon; import com.google.inject.AbstractModule; import com.google.inject.Singleton; import com.google.inject.multibindings.ProvidesIntoSet; -import org.geysermc.floodgate.addon.AddonManagerAddon; -import org.geysermc.floodgate.addon.DebugAddon; import org.geysermc.floodgate.api.inject.InjectorAddon; -import org.geysermc.floodgate.register.AddonRegister; +import org.geysermc.floodgate.core.addon.AddonManagerAddon; +import org.geysermc.floodgate.core.addon.DebugAddon; +import org.geysermc.floodgate.core.register.AddonRegister; +import org.geysermc.floodgate.mod.data.ModDataAddon; -public final class FabricAddonModule extends AbstractModule { +public final class ModAddonModule extends AbstractModule { @Override protected void configure() { bind(AddonRegister.class).asEagerSingleton(); @@ -24,7 +24,7 @@ public InjectorAddon managerAddon() { @Singleton @ProvidesIntoSet public InjectorAddon dataAddon() { - return new FabricDataAddon(); + return new ModDataAddon(); } @Singleton diff --git a/mod/src/main/java/org/geysermc/floodgate/mod/module/ModListenerModule.java b/mod/src/main/java/org/geysermc/floodgate/mod/module/ModListenerModule.java new file mode 100644 index 0000000..98d4532 --- /dev/null +++ b/mod/src/main/java/org/geysermc/floodgate/mod/module/ModListenerModule.java @@ -0,0 +1,21 @@ +package org.geysermc.floodgate.mod.module; + +import com.google.inject.AbstractModule; +import com.google.inject.Singleton; +import com.google.inject.TypeLiteral; +import com.google.inject.multibindings.ProvidesIntoSet; +import org.geysermc.floodgate.core.register.ListenerRegister; +import org.geysermc.floodgate.mod.listener.ModEventListener; + +public final class ModListenerModule extends AbstractModule { + @Override + protected void configure() { + bind(new TypeLiteral>() {}).asEagerSingleton(); + } + + @Singleton + @ProvidesIntoSet + public ModEventListener modEventListener() { + return new ModEventListener(); + } +} diff --git a/mod/src/main/java/org/geysermc/floodgate/mod/module/ModPlatformModule.java b/mod/src/main/java/org/geysermc/floodgate/mod/module/ModPlatformModule.java new file mode 100644 index 0000000..534849a --- /dev/null +++ b/mod/src/main/java/org/geysermc/floodgate/mod/module/ModPlatformModule.java @@ -0,0 +1,77 @@ +package org.geysermc.floodgate.mod.module; + +import com.google.inject.AbstractModule; +import com.google.inject.Provides; +import com.google.inject.Singleton; +import com.google.inject.name.Named; +import com.google.inject.name.Names; +import lombok.RequiredArgsConstructor; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.geysermc.floodgate.api.FloodgateApi; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.core.inject.CommonPlatformInjector; +import org.geysermc.floodgate.core.platform.command.CommandUtil; +import org.geysermc.floodgate.core.platform.util.PlatformUtils; +import org.geysermc.floodgate.core.skin.SkinApplier; +import org.geysermc.floodgate.core.util.LanguageManager; +import org.geysermc.floodgate.mod.FloodgateMod; +import org.geysermc.floodgate.mod.inject.ModInjector; +import org.geysermc.floodgate.mod.logger.Log4jFloodgateLogger; +import org.geysermc.floodgate.mod.pluginmessage.ModSkinApplier; +import org.geysermc.floodgate.mod.util.ModCommandUtil; +import org.geysermc.floodgate.mod.util.ModPlatformUtils; + +@RequiredArgsConstructor +public abstract class ModPlatformModule extends AbstractModule { + + @Provides + @Singleton + public CommandUtil commandUtil( + FloodgateApi api, + FloodgateLogger logger, + LanguageManager languageManager) { + return new ModCommandUtil(languageManager, api, logger); + } + + @Override + protected void configure() { + bind(PlatformUtils.class).to(ModPlatformUtils.class); + bind(Logger.class).annotatedWith(Names.named("logger")).toInstance(LogManager.getLogger("floodgate")); + bind(FloodgateLogger.class).to(Log4jFloodgateLogger.class); + } + + /* + DebugAddon / PlatformInjector + */ + + @Provides + @Singleton + public CommonPlatformInjector platformInjector() { + return ModInjector.INSTANCE; + } + + @Provides + @Named("packetEncoder") + public String packetEncoder() { + return FloodgateMod.INSTANCE.isClient() ? "encoder" : "outbound_config"; + } + + @Provides + @Named("packetDecoder") + public String packetDecoder() { + return FloodgateMod.INSTANCE.isClient() ? "inbound_config" : "decoder" ; + } + + @Provides + @Named("packetHandler") + public String packetHandler() { + return "packet_handler"; + } + + @Provides + @Singleton + public SkinApplier skinApplier() { + return new ModSkinApplier(); + } +} diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java b/mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/ModSkinApplier.java similarity index 90% rename from src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java rename to mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/ModSkinApplier.java index 374c2d8..4537172 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/ModSkinApplier.java @@ -1,4 +1,4 @@ -package org.geysermc.floodgate.pluginmessage; +package org.geysermc.floodgate.mod.pluginmessage; import com.mojang.authlib.properties.Property; import com.mojang.authlib.properties.PropertyMap; @@ -8,16 +8,16 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.floodgate.MinecraftServerHolder; import org.geysermc.floodgate.api.player.FloodgatePlayer; -import org.geysermc.floodgate.mixin.ChunkMapMixin; -import org.geysermc.floodgate.skin.SkinApplier; - -import static org.geysermc.floodgate.api.event.skin.SkinApplyEvent.SkinData; +import org.geysermc.floodgate.core.skin.SkinApplier; +import org.geysermc.floodgate.mod.MinecraftServerHolder; +import org.geysermc.floodgate.mod.mixin.ChunkMapMixin; import java.util.Collections; -public final class FabricSkinApplier implements SkinApplier { +import static org.geysermc.floodgate.api.event.skin.SkinApplyEvent.SkinData; + +public final class ModSkinApplier implements SkinApplier { @Override public void applySkin(@NonNull FloodgatePlayer floodgatePlayer, @NonNull SkinData skinData) { diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/FormPayload.java b/mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/payloads/FormPayload.java similarity index 94% rename from src/main/java/org/geysermc/floodgate/pluginmessage/payloads/FormPayload.java rename to mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/payloads/FormPayload.java index 475d138..fa7b727 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/FormPayload.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/payloads/FormPayload.java @@ -1,4 +1,4 @@ -package org.geysermc.floodgate.pluginmessage.payloads; +package org.geysermc.floodgate.mod.pluginmessage.payloads; import io.netty.buffer.ByteBufUtil; import net.minecraft.network.FriendlyByteBuf; diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/PacketPayload.java b/mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/payloads/PacketPayload.java similarity index 94% rename from src/main/java/org/geysermc/floodgate/pluginmessage/payloads/PacketPayload.java rename to mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/payloads/PacketPayload.java index b00021b..944ea86 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/PacketPayload.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/payloads/PacketPayload.java @@ -1,4 +1,4 @@ -package org.geysermc.floodgate.pluginmessage.payloads; +package org.geysermc.floodgate.mod.pluginmessage.payloads; import io.netty.buffer.ByteBufUtil; import net.minecraft.network.FriendlyByteBuf; diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/SkinPayload.java b/mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/payloads/SkinPayload.java similarity index 94% rename from src/main/java/org/geysermc/floodgate/pluginmessage/payloads/SkinPayload.java rename to mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/payloads/SkinPayload.java index a1e6eee..16fda96 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/SkinPayload.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/payloads/SkinPayload.java @@ -1,4 +1,4 @@ -package org.geysermc.floodgate.pluginmessage.payloads; +package org.geysermc.floodgate.mod.pluginmessage.payloads; import io.netty.buffer.ByteBufUtil; import net.minecraft.network.FriendlyByteBuf; diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/TransferPayload.java b/mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/payloads/TransferPayload.java similarity index 94% rename from src/main/java/org/geysermc/floodgate/pluginmessage/payloads/TransferPayload.java rename to mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/payloads/TransferPayload.java index 6396327..597634e 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/TransferPayload.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/payloads/TransferPayload.java @@ -1,4 +1,4 @@ -package org.geysermc.floodgate.pluginmessage.payloads; +package org.geysermc.floodgate.mod.pluginmessage.payloads; import io.netty.buffer.ByteBufUtil; import net.minecraft.network.FriendlyByteBuf; diff --git a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java b/mod/src/main/java/org/geysermc/floodgate/mod/util/ModCommandUtil.java similarity index 82% rename from src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java rename to mod/src/main/java/org/geysermc/floodgate/mod/util/ModCommandUtil.java index 0382148..c5685d2 100644 --- a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/util/ModCommandUtil.java @@ -1,28 +1,30 @@ -package org.geysermc.floodgate.util; +package org.geysermc.floodgate.mod.util; import com.mojang.authlib.GameProfile; -import me.lucko.fabric.api.permissions.v0.Permissions; -import net.minecraft.commands.CommandSource; +import lombok.Setter; import net.minecraft.commands.CommandSourceStack; -import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.players.UserWhiteListEntry; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.floodgate.MinecraftServerHolder; import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.platform.command.CommandUtil; -import org.geysermc.floodgate.player.UserAudience; +import org.geysermc.floodgate.core.platform.command.CommandUtil; +import org.geysermc.floodgate.core.player.UserAudience; +import org.geysermc.floodgate.core.util.LanguageManager; +import org.geysermc.floodgate.mod.MinecraftServerHolder; +import org.incendo.cloud.CommandManager; -import java.util.*; -import java.util.logging.Logger; +import java.util.Collection; +import java.util.UUID; -public final class FabricCommandUtil extends CommandUtil { +public final class ModCommandUtil extends CommandUtil { private final FloodgateLogger logger; private UserAudience console; + @Setter + private CommandManager commandManager; - public FabricCommandUtil(LanguageManager manager, FloodgateApi api, FloodgateLogger logger) { + public ModCommandUtil(LanguageManager manager, FloodgateApi api, FloodgateLogger logger) { super(manager, api); this.logger = logger; } @@ -73,8 +75,7 @@ protected Collection getOnlinePlayers() { @Override public boolean hasPermission(Object source, String permission) { - return Permissions.check((SharedSuggestionProvider) source, - permission, MinecraftServerHolder.get().getOperatorUserPermissionLevel()); + return commandManager.hasPermission(getUserAudience(source), permission); } @Override diff --git a/src/main/java/org/geysermc/floodgate/util/MixinConfigPlugin.java b/mod/src/main/java/org/geysermc/floodgate/mod/util/ModMixinConfigPlugin.java similarity index 56% rename from src/main/java/org/geysermc/floodgate/util/MixinConfigPlugin.java rename to mod/src/main/java/org/geysermc/floodgate/mod/util/ModMixinConfigPlugin.java index b8ee162..47945b7 100644 --- a/src/main/java/org/geysermc/floodgate/util/MixinConfigPlugin.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/util/ModMixinConfigPlugin.java @@ -1,6 +1,6 @@ -package org.geysermc.floodgate.util; +package org.geysermc.floodgate.mod.util; -import net.fabricmc.loader.api.FabricLoader; +import dev.architectury.injectables.annotations.ExpectPlatform; import org.objectweb.asm.tree.ClassNode; import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; import org.spongepowered.asm.mixin.extensibility.IMixinInfo; @@ -8,7 +8,7 @@ import java.util.List; import java.util.Set; -public class MixinConfigPlugin implements IMixinConfigPlugin { +public class ModMixinConfigPlugin implements IMixinConfigPlugin { @Override public void onLoad(String mixinPackage) { @@ -21,12 +21,11 @@ public String getRefMapperConfig() { @Override public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { - if (mixinClassName.equals("org.geysermc.floodgate.mixin.ClientIntentionPacketMixin")) { - //returns true if fabricproxy-lite is present, therefore loading the mixin. If not present, the mixin will not be loaded. - return FabricLoader.getInstance().isModLoaded("fabricproxy-lite"); + if (mixinClassName.equals("org.geysermc.floodgate.mod.mixin.ClientIntentionPacketMixin")) { + return applyProxyFix(); } - if (mixinClassName.equals("org.geysermc.floodgate.mixin.GeyserModInjectorMixin")) { - return FabricLoader.getInstance().isModLoaded("geyser-fabric"); + if (mixinClassName.equals("org.geysermc.floodgate.mod.mixin.GeyserModInjectorMixin")) { + return isGeyserLoaded(); } return true; } @@ -47,4 +46,14 @@ public void preApply(String targetClassName, ClassNode targetClass, String mixin @Override public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { } + + @ExpectPlatform + public static boolean isGeyserLoaded() { + throw new IllegalStateException("isGeyserLoaded is not implemented!"); + } + + @ExpectPlatform + public static boolean applyProxyFix() { + throw new IllegalStateException("applyProxyFix is not implemented!"); + } } \ No newline at end of file diff --git a/src/main/java/org/geysermc/floodgate/util/FabricPlatformUtils.java b/mod/src/main/java/org/geysermc/floodgate/mod/util/ModPlatformUtils.java similarity index 67% rename from src/main/java/org/geysermc/floodgate/util/FabricPlatformUtils.java rename to mod/src/main/java/org/geysermc/floodgate/mod/util/ModPlatformUtils.java index 900ec2d..6ab2dfd 100644 --- a/src/main/java/org/geysermc/floodgate/util/FabricPlatformUtils.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/util/ModPlatformUtils.java @@ -1,10 +1,10 @@ -package org.geysermc.floodgate.util; +package org.geysermc.floodgate.mod.util; import net.minecraft.SharedConstants; -import org.geysermc.floodgate.MinecraftServerHolder; -import org.geysermc.floodgate.platform.util.PlatformUtils; +import org.geysermc.floodgate.core.platform.util.PlatformUtils; +import org.geysermc.floodgate.mod.MinecraftServerHolder; -public class FabricPlatformUtils extends PlatformUtils { +public class ModPlatformUtils extends PlatformUtils { @Override public AuthType authType() { return MinecraftServerHolder.get().usesAuthentication() ? AuthType.ONLINE : AuthType.OFFLINE; diff --git a/mod/src/main/java/org/geysermc/floodgate/mod/util/ModTemplateReader.java b/mod/src/main/java/org/geysermc/floodgate/mod/util/ModTemplateReader.java new file mode 100644 index 0000000..9f0d4df --- /dev/null +++ b/mod/src/main/java/org/geysermc/floodgate/mod/util/ModTemplateReader.java @@ -0,0 +1,25 @@ +package org.geysermc.floodgate.mod.util; + +import org.geysermc.configutils.file.template.TemplateReader; +import org.geysermc.floodgate.mod.FloodgateMod; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class ModTemplateReader implements TemplateReader { + + @Override + public BufferedReader read(String configName) { + Path path = FloodgateMod.INSTANCE.resourcePath(configName); + if (path != null) { + try { + return Files.newBufferedReader(path); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + return null; + } +} diff --git a/src/main/resources/floodgate.accesswidener b/mod/src/main/resources/floodgate.accesswidener similarity index 100% rename from src/main/resources/floodgate.accesswidener rename to mod/src/main/resources/floodgate.accesswidener diff --git a/src/main/resources/floodgate.mixins.json b/mod/src/main/resources/floodgate.mixins.json similarity index 69% rename from src/main/resources/floodgate.mixins.json rename to mod/src/main/resources/floodgate.mixins.json index ca8e559..d3597fb 100644 --- a/src/main/resources/floodgate.mixins.json +++ b/mod/src/main/resources/floodgate.mixins.json @@ -1,17 +1,18 @@ { "required": true, "minVersion": "0.8", - "package": "org.geysermc.floodgate.mixin", + "package": "org.geysermc.floodgate.mod.mixin", "compatibilityLevel": "JAVA_17", "mixins": [ "ChunkMapMixin", "ClientIntentionPacketMixin", "ClientIntentionPacketMixinInterface", "ConnectionMixin", + "FloodgateUtilMixin", "GeyserModInjectorMixin", "ServerConnectionListenerMixin" ], - "plugin": "org.geysermc.floodgate.util.MixinConfigPlugin", + "plugin": "org.geysermc.floodgate.mod.util.ModMixinConfigPlugin", "injectors": { "defaultRequire": 1 } diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts new file mode 100644 index 0000000..6f18ab5 --- /dev/null +++ b/neoforge/build.gradle.kts @@ -0,0 +1,61 @@ +architectury { + platformSetupLoomIde() + neoForge() +} + +provided("com.google.guava", "failureaccess") + +// Used to extend runtime/compile classpaths +val common: Configuration by configurations.creating +// Needed to read mixin config in the runServer task, and for the architectury transformer +// (e.g. the @ExpectPlatform annotation) +val developmentNeoForge: Configuration = configurations.getByName("developmentNeoForge") +// Our custom transitive include configuration +val includeTransitive: Configuration = configurations.getByName("includeTransitive") + +configurations { + compileClasspath.get().extendsFrom(configurations["common"]) + runtimeClasspath.get().extendsFrom(configurations["common"]) + developmentNeoForge.extendsFrom(configurations["common"]) +} + +dependencies { + // See https://github.com/google/guava/issues/6618 + modules { + module("com.google.guava:listenablefuture") { + replacedBy("com.google.guava:guava", "listenablefuture is part of guava") + } + } + + neoForge(libs.neoforge) + // "namedElements" configuration should be used to depend on different loom projects + common(project(":mod", configuration = "namedElements")) { isTransitive = false } + // Bundle transformed classes of the common module for production mod jar + shadow(project(path = ":mod", configuration = "transformProductionNeoForge")) { isTransitive = false } + + includeTransitive(libs.floodgate.core) + + implementation(libs.floodgate.core) + implementation(libs.guice) + + modImplementation(libs.cloud.neoforge) + include(libs.cloud.neoforge) +} + +tasks { + processResources { + from(project(":mod").file("src/main/resources/floodgate.accesswidener")) { + into("/assets/") + } + } + + remapJar { + dependsOn(processResources) + atAccessWideners.add("floodgate.accesswidener") + archiveBaseName.set("floodgate-neoforge") + } + + modrinth { + loaders.add("neoforge") + } +} diff --git a/neoforge/gradle.properties b/neoforge/gradle.properties new file mode 100644 index 0000000..2914393 --- /dev/null +++ b/neoforge/gradle.properties @@ -0,0 +1 @@ +loom.platform=neoforge \ No newline at end of file diff --git a/neoforge/src/main/java/org/geysermc/floodgate/mod/util/neoforge/ModMixinConfigPluginImpl.java b/neoforge/src/main/java/org/geysermc/floodgate/mod/util/neoforge/ModMixinConfigPluginImpl.java new file mode 100644 index 0000000..27159f6 --- /dev/null +++ b/neoforge/src/main/java/org/geysermc/floodgate/mod/util/neoforge/ModMixinConfigPluginImpl.java @@ -0,0 +1,13 @@ +package org.geysermc.floodgate.mod.util.neoforge; + +import net.neoforged.fml.loading.LoadingModList; + +public class ModMixinConfigPluginImpl { + public static boolean isGeyserLoaded() { + return LoadingModList.get().getModFileById("geyser_neoforge") != null; + } + + public static boolean applyProxyFix() { + return false; + } +} diff --git a/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/NeoForgeFloodgateMod.java b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/NeoForgeFloodgateMod.java new file mode 100644 index 0000000..e3d09d5 --- /dev/null +++ b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/NeoForgeFloodgateMod.java @@ -0,0 +1,101 @@ +package org.geysermc.floodgate.platform.neoforge; + +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.ModContainer; +import net.neoforged.fml.common.Mod; +import net.neoforged.fml.loading.FMLLoader; +import net.neoforged.fml.loading.FMLPaths; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.GameShuttingDownEvent; +import net.neoforged.neoforge.event.server.ServerStartedEvent; +import net.neoforged.neoforge.event.server.ServerStoppingEvent; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.core.module.PluginMessageModule; +import org.geysermc.floodgate.core.module.ServerCommonModule; +import org.geysermc.floodgate.mod.FloodgateMod; +import org.geysermc.floodgate.mod.util.ModTemplateReader; +import org.geysermc.floodgate.platform.neoforge.module.NeoForgeCommandModule; +import org.geysermc.floodgate.platform.neoforge.module.NeoForgePlatformModule; +import org.geysermc.floodgate.platform.neoforge.pluginmessage.NeoForgePluginMessageRegistration; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.nio.file.Path; +import java.util.Set; +import java.util.stream.Collectors; + +@Mod("floodgate") +public final class NeoForgeFloodgateMod extends FloodgateMod { + + private final ModContainer container; + + public NeoForgeFloodgateMod(IEventBus modEventBus, ModContainer container) { + this.container = container; + init( + new ServerCommonModule( + FMLPaths.CONFIGDIR.get().resolve("floodgate"), + new ModTemplateReader() + ), + new NeoForgePlatformModule(), + new NeoForgeCommandModule() + ); + + modEventBus.addListener(this::onRegisterPackets); + NeoForge.EVENT_BUS.addListener(this::onServerStarted); + if (FMLLoader.getDist().isClient()) { + NeoForge.EVENT_BUS.addListener(this::onClientStop); + } else { + NeoForge.EVENT_BUS.addListener(this::onServerStop); + } + } + + private void onServerStarted(ServerStartedEvent event) { + this.enable(event.getServer()); + } + + private void onClientStop(GameShuttingDownEvent ignored) { + this.disable(); + } + + private void onServerStop(ServerStoppingEvent ignored) { + this.disable(); + } + + private void onRegisterPackets(final RegisterPayloadHandlersEvent event) { + // Set the registrar once we're given it - NeoForgePluginMessageRegistration was created earlier in NeoForgePlatformModule + NeoForgePluginMessageRegistration pluginMessageRegistration = injector.getInstance(NeoForgePluginMessageRegistration.class); + pluginMessageRegistration.setRegistrar(event.registrar("floodgate").optional()); + + // We can now trigger the registering of our plugin message channels + enable(new PluginMessageModule()); + } + + @Override + public @Nullable Path resourcePath(String file) { + return container.getModInfo().getOwningFile().getFile().findResource(file); + } + + @Override + public boolean isClient() { + return FMLLoader.getDist().isClient(); + } + + public Set> getAnnotatedClasses(Class annotationClass) { + return container.getModInfo() + .getOwningFile() + .getFile() + .getScanResult() + .getAnnotatedBy(annotationClass, ElementType.TYPE) + .map(annotationData -> { + try { + return Class.forName(annotationData.clazz().getClassName()); + } catch (Exception e) { + injector.getInstance(FloodgateLogger.class).error(e.getMessage(), e); + return null; + } + }) + .collect(Collectors.toSet()); + } +} diff --git a/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/listener/NeoForgeEventRegistration.java b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/listener/NeoForgeEventRegistration.java new file mode 100644 index 0000000..77afc91 --- /dev/null +++ b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/listener/NeoForgeEventRegistration.java @@ -0,0 +1,20 @@ +package org.geysermc.floodgate.platform.neoforge.listener; + +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.entity.player.PlayerEvent; +import org.geysermc.floodgate.core.platform.listener.ListenerRegistration; +import org.geysermc.floodgate.mod.listener.ModEventListener; + +public final class NeoForgeEventRegistration implements ListenerRegistration { + private ModEventListener listener; + + @Override + public void register(ModEventListener listener) { + NeoForge.EVENT_BUS.addListener(this::onPlayerJoin); + this.listener = listener; + } + + private void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) { + listener.onPlayerJoin(event.getEntity().getUUID()); + } +} diff --git a/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/mixin/NeoForgeFloodgateUtilMixin.java b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/mixin/NeoForgeFloodgateUtilMixin.java new file mode 100644 index 0000000..cee3218 --- /dev/null +++ b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/mixin/NeoForgeFloodgateUtilMixin.java @@ -0,0 +1,27 @@ +package org.geysermc.floodgate.platform.neoforge.mixin; + +import org.geysermc.floodgate.core.util.Utils; +import org.geysermc.floodgate.mod.FloodgateMod; +import org.geysermc.floodgate.platform.neoforge.NeoForgeFloodgateMod; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; + +import java.lang.annotation.Annotation; +import java.util.Set; + +/** + * Mixin into Floodgate's {@link Utils} class as NeoForge is really picky about how it allows scanning + * mod-owned classes. + */ +@Mixin(value = Utils.class, remap = false) +public class NeoForgeFloodgateUtilMixin { + + /** + * @author geysermc + * @reason NeoForge is really picky about how it allows scanning mod-owned classes. + */ + @Overwrite(remap = false) + public static Set> getGeneratedClassesForAnnotation(Class annotationClass) { + return ((NeoForgeFloodgateMod) FloodgateMod.INSTANCE).getAnnotatedClasses(annotationClass); + } +} diff --git a/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/module/NeoForgeCommandModule.java b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/module/NeoForgeCommandModule.java new file mode 100644 index 0000000..5b06fe8 --- /dev/null +++ b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/module/NeoForgeCommandModule.java @@ -0,0 +1,29 @@ +package org.geysermc.floodgate.platform.neoforge.module; + +import com.google.inject.Provides; +import com.google.inject.Singleton; +import lombok.SneakyThrows; +import org.geysermc.floodgate.core.module.CommandModule; +import org.geysermc.floodgate.core.platform.command.CommandUtil; +import org.geysermc.floodgate.core.player.FloodgateCommandPreprocessor; +import org.geysermc.floodgate.core.player.UserAudience; +import org.geysermc.floodgate.core.player.audience.FloodgateSenderMapper; +import org.geysermc.floodgate.mod.util.ModCommandUtil; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.execution.ExecutionCoordinator; +import org.incendo.cloud.neoforge.NeoForgeServerCommandManager; + +public class NeoForgeCommandModule extends CommandModule { + @Provides + @Singleton + @SneakyThrows + public CommandManager commandManager(CommandUtil commandUtil) { + CommandManager commandManager = new NeoForgeServerCommandManager<>( + ExecutionCoordinator.simpleCoordinator(), + new FloodgateSenderMapper<>(commandUtil) + ); + commandManager.registerCommandPreProcessor(new FloodgateCommandPreprocessor<>(commandUtil)); + ((ModCommandUtil) commandUtil).setCommandManager(commandManager); + return commandManager; + } +} diff --git a/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/module/NeoForgePlatformModule.java b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/module/NeoForgePlatformModule.java new file mode 100644 index 0000000..f224ad0 --- /dev/null +++ b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/module/NeoForgePlatformModule.java @@ -0,0 +1,46 @@ +package org.geysermc.floodgate.platform.neoforge.module; + +import com.google.inject.Provides; +import com.google.inject.Scopes; +import com.google.inject.Singleton; +import com.google.inject.name.Named; +import org.geysermc.floodgate.core.platform.listener.ListenerRegistration; +import org.geysermc.floodgate.core.platform.pluginmessage.PluginMessageUtils; +import org.geysermc.floodgate.core.pluginmessage.PluginMessageRegistration; +import org.geysermc.floodgate.mod.listener.ModEventListener; +import org.geysermc.floodgate.mod.module.ModPlatformModule; +import org.geysermc.floodgate.platform.neoforge.listener.NeoForgeEventRegistration; +import org.geysermc.floodgate.platform.neoforge.pluginmessage.NeoForgePluginMessageRegistration; +import org.geysermc.floodgate.platform.neoforge.pluginmessage.NeoForgePluginMessageUtils; + +public class NeoForgePlatformModule extends ModPlatformModule { + + @Override + protected void configure() { + super.configure(); + + // We retrieve using NeoForgePluginMessageRegistration.class from our the mod class. + // We do this to ensure that injector#getInstance with either class returns the same singleton + bind(PluginMessageRegistration.class).to(NeoForgePluginMessageRegistration.class).in(Scopes.SINGLETON); + bind(NeoForgePluginMessageRegistration.class).toInstance(new NeoForgePluginMessageRegistration()); + } + + @Provides + @Singleton + public ListenerRegistration listenerRegistration() { + return new NeoForgeEventRegistration(); + } + + @Provides + @Singleton + public PluginMessageUtils pluginMessageUtils() { + return new NeoForgePluginMessageUtils(); + } + + @Provides + @Named("implementationName") + public String implementationName() { + return "NeoForge"; + } + +} diff --git a/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/pluginmessage/NeoForgePluginMessageRegistration.java b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/pluginmessage/NeoForgePluginMessageRegistration.java new file mode 100644 index 0000000..bd73bb3 --- /dev/null +++ b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/pluginmessage/NeoForgePluginMessageRegistration.java @@ -0,0 +1,39 @@ +package org.geysermc.floodgate.platform.neoforge.pluginmessage; + +import lombok.Setter; +import net.neoforged.neoforge.network.registration.PayloadRegistrar; +import org.geysermc.floodgate.core.pluginmessage.PluginMessageChannel; +import org.geysermc.floodgate.core.pluginmessage.PluginMessageRegistration; +import org.geysermc.floodgate.mod.pluginmessage.payloads.FormPayload; +import org.geysermc.floodgate.mod.pluginmessage.payloads.PacketPayload; +import org.geysermc.floodgate.mod.pluginmessage.payloads.SkinPayload; +import org.geysermc.floodgate.mod.pluginmessage.payloads.TransferPayload; + +public class NeoForgePluginMessageRegistration implements PluginMessageRegistration { + + @Setter + private PayloadRegistrar registrar; + + @Override + public void register(PluginMessageChannel channel) { + switch (channel.getIdentifier()) { + case "floodgate:form" -> + registrar.playBidirectional(FormPayload.TYPE, FormPayload.STREAM_CODEC, (payload, context) -> + channel.handleServerCall(payload.data(), context.player().getUUID(), + context.player().getGameProfile().getName())); + case "floodgate:packet" -> + registrar.playBidirectional(PacketPayload.TYPE, PacketPayload.STREAM_CODEC, (payload, context) -> + channel.handleServerCall(payload.data(), context.player().getUUID(), + context.player().getGameProfile().getName())); + case "floodgate:skin" -> + registrar.playBidirectional(SkinPayload.TYPE, SkinPayload.STREAM_CODEC, (payload, context) -> + channel.handleServerCall(payload.data(), context.player().getUUID(), + context.player().getGameProfile().getName())); + case "floodgate:transfer" -> + registrar.playBidirectional(TransferPayload.TYPE, TransferPayload.STREAM_CODEC, (payload, context) -> + channel.handleServerCall(payload.data(), context.player().getUUID(), + context.player().getGameProfile().getName())); + default -> throw new IllegalArgumentException("unknown channel: " + channel); + } + } +} diff --git a/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/pluginmessage/NeoForgePluginMessageUtils.java b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/pluginmessage/NeoForgePluginMessageUtils.java new file mode 100644 index 0000000..5faf4d5 --- /dev/null +++ b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/pluginmessage/NeoForgePluginMessageUtils.java @@ -0,0 +1,37 @@ +package org.geysermc.floodgate.platform.neoforge.pluginmessage; + +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.server.level.ServerPlayer; +import net.neoforged.neoforge.network.PacketDistributor; +import org.geysermc.floodgate.core.platform.pluginmessage.PluginMessageUtils; +import org.geysermc.floodgate.mod.MinecraftServerHolder; +import org.geysermc.floodgate.mod.pluginmessage.payloads.FormPayload; +import org.geysermc.floodgate.mod.pluginmessage.payloads.PacketPayload; +import org.geysermc.floodgate.mod.pluginmessage.payloads.SkinPayload; +import org.geysermc.floodgate.mod.pluginmessage.payloads.TransferPayload; + +import java.util.Objects; +import java.util.UUID; + +public class NeoForgePluginMessageUtils extends PluginMessageUtils { + public boolean sendMessage(UUID uuid, String channel, byte[] data) { + try { + ServerPlayer player = MinecraftServerHolder.get().getPlayerList().getPlayer(uuid); + final CustomPacketPayload payload; + switch (channel) { + case "floodgate:form" -> payload = new FormPayload(data); + case "floodgate:packet" -> payload = new PacketPayload(data); + case "floodgate:skin" -> payload = new SkinPayload(data); + case "floodgate:transfer" -> payload = new TransferPayload(data); + default -> throw new IllegalArgumentException("unknown channel: " + channel); + } + + Objects.requireNonNull(player); + PacketDistributor.sendToPlayer(player, payload); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + return true; + } +} diff --git a/neoforge/src/main/resources/META-INF/neoforge.mods.toml b/neoforge/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 0000000..04901ca --- /dev/null +++ b/neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,27 @@ +modLoader="javafml" +loaderVersion="[1,)" +license="MIT" +[[mods]] +modId="$id" +version="$version" +displayName="$name" +displayURL="$url" +logoFile= "../assets/floodgate/icon.png" +authors="$author" +description="$description" +[[mixins]] +config = "floodgate.mixins.json" +[[mixins]] +config = "floodgate_neoforge.mixins.json" +[[dependencies.floodgate]] +modId="neoforge" +type="required" +versionRange="[21.0.0-beta,)" +ordering="NONE" +side="BOTH" +[[dependencies.floodgate]] +modId="minecraft" +type="required" +versionRange="[$minecraft_version,)" +ordering="NONE" +side="BOTH" \ No newline at end of file diff --git a/neoforge/src/main/resources/floodgate_neoforge.mixins.json b/neoforge/src/main/resources/floodgate_neoforge.mixins.json new file mode 100644 index 0000000..9c06f8d --- /dev/null +++ b/neoforge/src/main/resources/floodgate_neoforge.mixins.json @@ -0,0 +1,12 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "org.geysermc.floodgate.platform.neoforge.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "NeoForgeFloodgateUtilMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 71ef923..5d93e26 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,8 +1,34 @@ +@file:Suppress("UnstableApiUsage") + +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") + pluginManagement { repositories { - //mavenLocal() - mavenCentral() gradlePluginPortal() + maven("https://repo.opencollab.dev/main/") + maven("https://jitpack.io") { + content { + includeGroupByRegex("com\\.github\\..*") + } + } + + maven("https://maven.architectury.dev/") + maven("https://maven.neoforged.net/releases") maven("https://maven.fabricmc.net/") } + + plugins { + id("net.kyori.blossom") + id("net.kyori.indra") + id("net.kyori.indra.git") + id("floodgate-modded.build-logic") + } + + includeBuild("build-logic") } + +rootProject.name = "floodgate-modded" + +include(":mod") +include(":fabric") +include(":neoforge") diff --git a/src/main/java/org/geysermc/floodgate/FabricMod.java b/src/main/java/org/geysermc/floodgate/FabricMod.java deleted file mode 100644 index e93365f..0000000 --- a/src/main/java/org/geysermc/floodgate/FabricMod.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.geysermc.floodgate; - -import net.fabricmc.api.EnvType; -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; -import org.geysermc.floodgate.inject.fabric.FabricInjector; -import com.google.inject.Guice; -import com.google.inject.Injector; -import net.fabricmc.api.ModInitializer; -import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; -import net.fabricmc.loader.api.FabricLoader; -import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.module.*; -import org.geysermc.floodgate.util.FabricTemplateReader; - -public class FabricMod implements ModInitializer { - - private boolean started; - - @Override - public void onInitialize() { - FabricInjector.setInstance(new FabricInjector()); - - Injector injector = Guice.createInjector( - new ServerCommonModule(FabricLoader.getInstance().getConfigDir().resolve("floodgate"), new FabricTemplateReader()), - new FabricPlatformModule() - ); - - FloodgatePlatform platform = injector.getInstance(FloodgatePlatform.class); - - platform.enable(new FabricCommandModule()); - - ServerLifecycleEvents.SERVER_STARTED.register((server) -> { - long ctm = System.currentTimeMillis(); - - // Stupid hack, see the class for more information - // This can probably be Guice-i-fied but that is beyond me - MinecraftServerHolder.set(server); - - if (!started) { - platform.enable( - new FabricAddonModule(), - new FabricListenerModule(), - new PluginMessageModule() - ); - started = true; - } - - long endCtm = System.currentTimeMillis(); - injector.getInstance(FloodgateLogger.class) - .translatedInfo("floodgate.core.finish", endCtm - ctm); - }); - - if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { - ClientLifecycleEvents.CLIENT_STOPPING.register(($) -> { - platform.disable(); - }); - } else { - ServerLifecycleEvents.SERVER_STOPPING.register((server) -> { - platform.disable(); - }); - } - } -} diff --git a/src/main/java/org/geysermc/floodgate/listener/FabricEventRegistration.java b/src/main/java/org/geysermc/floodgate/listener/FabricEventRegistration.java deleted file mode 100644 index 43daf0f..0000000 --- a/src/main/java/org/geysermc/floodgate/listener/FabricEventRegistration.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.geysermc.floodgate.listener; - -import com.google.inject.Inject; -import lombok.RequiredArgsConstructor; -import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; -import org.geysermc.floodgate.platform.listener.ListenerRegistration; - -@RequiredArgsConstructor(onConstructor = @__(@Inject)) -public final class FabricEventRegistration implements ListenerRegistration { - @Override - public void register(FabricEventListener listener) { - ServerPlayConnectionEvents.JOIN.register(listener::onPlayerJoin); - } -} diff --git a/src/main/java/org/geysermc/floodgate/module/FabricListenerModule.java b/src/main/java/org/geysermc/floodgate/module/FabricListenerModule.java deleted file mode 100644 index ec67abc..0000000 --- a/src/main/java/org/geysermc/floodgate/module/FabricListenerModule.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.geysermc.floodgate.module; - -import com.google.inject.AbstractModule; -import com.google.inject.Singleton; -import com.google.inject.TypeLiteral; -import com.google.inject.multibindings.ProvidesIntoSet; -import org.geysermc.floodgate.listener.FabricEventListener; -import org.geysermc.floodgate.register.ListenerRegister; - -public final class FabricListenerModule extends AbstractModule { - @Override - protected void configure() { - bind(new TypeLiteral>() {}).asEagerSingleton(); - } - - @Singleton - @ProvidesIntoSet - public FabricEventListener fabricEventListener() { - return new FabricEventListener(); - } -} diff --git a/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java b/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java deleted file mode 100644 index 9a59283..0000000 --- a/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java +++ /dev/null @@ -1,107 +0,0 @@ -package org.geysermc.floodgate.module; - -import com.google.inject.name.Names; -import org.apache.logging.log4j.Logger; -import org.geysermc.floodgate.inject.fabric.FabricInjector; -import org.geysermc.floodgate.listener.FabricEventListener; -import org.geysermc.floodgate.listener.FabricEventRegistration; -import org.geysermc.floodgate.logger.Log4jFloodgateLogger; -import org.geysermc.floodgate.platform.listener.ListenerRegistration; -import org.geysermc.floodgate.platform.pluginmessage.PluginMessageUtils; -import org.geysermc.floodgate.platform.util.PlatformUtils; -import org.geysermc.floodgate.pluginmessage.FabricPluginMessageRegistration; -import org.geysermc.floodgate.pluginmessage.FabricPluginMessageUtils; -import org.geysermc.floodgate.pluginmessage.FabricSkinApplier; -import org.geysermc.floodgate.pluginmessage.PluginMessageRegistration; -import org.geysermc.floodgate.util.FabricCommandUtil; -import com.google.inject.AbstractModule; -import com.google.inject.Provides; -import com.google.inject.Singleton; -import com.google.inject.name.Named; -import lombok.RequiredArgsConstructor; -import org.apache.logging.log4j.LogManager; -import org.geysermc.floodgate.api.FloodgateApi; -import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.inject.CommonPlatformInjector; -import org.geysermc.floodgate.platform.command.CommandUtil; -import org.geysermc.floodgate.skin.SkinApplier; -import org.geysermc.floodgate.util.FabricPlatformUtils; -import org.geysermc.floodgate.util.LanguageManager; - -@RequiredArgsConstructor -public final class FabricPlatformModule extends AbstractModule { - - @Override - protected void configure() { - bind(PlatformUtils.class).to(FabricPlatformUtils.class); - bind(Logger.class).annotatedWith(Names.named("logger")).toInstance(LogManager.getLogger("floodgate")); - bind(FloodgateLogger.class).to(Log4jFloodgateLogger.class); - } - - @Provides - @Singleton - public CommandUtil commandUtil( - FloodgateApi api, - FloodgateLogger logger, - LanguageManager languageManager) { - return new FabricCommandUtil(languageManager, api, logger); - } - - @Provides - @Singleton - public ListenerRegistration listenerRegistration() { - return new FabricEventRegistration(); - } - - /* - DebugAddon / PlatformInjector - */ - - @Provides - @Singleton - public CommonPlatformInjector platformInjector() { - return FabricInjector.getInstance(); - } - - @Provides - @Named("packetEncoder") - public String packetEncoder() { - return "encoder"; - } - - @Provides - @Named("packetDecoder") - public String packetDecoder() { - return "decoder"; - } - - @Provides - @Named("packetHandler") - public String packetHandler() { - return "packet_handler"; - } - - @Provides - @Singleton - public PluginMessageUtils pluginMessageUtils() { - return new FabricPluginMessageUtils(); - } - - @Provides - @Named("implementationName") - public String implementationName() { - return "Fabric"; - } - - @Provides - @Singleton - public PluginMessageRegistration pluginMessageRegister() { - return new FabricPluginMessageRegistration(); - } - - @Provides - @Singleton - public SkinApplier skinApplier() { - return new FabricSkinApplier(); - } -} diff --git a/src/main/java/org/geysermc/floodgate/util/FabricTemplateReader.java b/src/main/java/org/geysermc/floodgate/util/FabricTemplateReader.java deleted file mode 100644 index b772fde..0000000 --- a/src/main/java/org/geysermc/floodgate/util/FabricTemplateReader.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.geysermc.floodgate.util; - -import net.fabricmc.loader.api.FabricLoader; -import net.fabricmc.loader.api.ModContainer; -import org.geysermc.configutils.file.template.TemplateReader; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.util.Optional; - -public class FabricTemplateReader implements TemplateReader { - - private final ModContainer container; - - public FabricTemplateReader() { - container = FabricLoader.getInstance().getModContainer("floodgate").orElseThrow(); - } - - @Override - public BufferedReader read(String configName) { - Optional optional = container.findPath(configName); - if (optional.isPresent()) { - try { - InputStream stream = optional.get().getFileSystem() - .provider() - .newInputStream(optional.get()); - return new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8)); - } catch (IOException e) { - throw new IllegalStateException(e); - } - } - return null; - } -} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json deleted file mode 100644 index 24b3009..0000000 --- a/src/main/resources/fabric.mod.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "schemaVersion": 1, - "id": "floodgate", - "version": "${version}", - "name": "Floodgate-Fabric", - "description": "", - "authors": [ - "GeyserMC" - ], - "contact": { - "website": "https://geysermc.org", - "repo": "https://github.com/GeyserMC/Floodgate-Fabric" - }, - "license": "MIT", - "icon": "assets/floodgate/icon.png", - "environment": "*", - "entrypoints": { - "main": [ - "org.geysermc.floodgate.FabricMod" - ] - }, - "accessWidener": "floodgate.accesswidener", - "mixins": [ - "floodgate.mixins.json" - ], - "depends": { - "fabricloader": ">=0.15.11", - "fabric": "*", - "minecraft": ">=1.21" - } -} \ No newline at end of file