From 52866232c68912eb535e8a1cdc1e1080d5b426c8 Mon Sep 17 00:00:00 2001 From: Magi1053 Date: Fri, 14 Aug 2026 11:15:39 -0500 Subject: [PATCH] Add native NeoForge 26.2 module via Forgified Fabric API --- .github/workflows/ci.yml | 6 + .github/workflows/on-release.yml | 38 +++- Plan/build.gradle | 3 + .../plan/commands/FabricCommandManager.java | 6 +- .../listeners/FabricListenerSystem.java | 3 +- .../listeners/events/PlanFabricEvents.java | 6 +- Plan/neoforge/build.gradle | 168 ++++++++++++++++ .../playeranalytics/plan/PlanNeoForge.java | 180 ++++++++++++++++++ .../plan/PlanNeoForgeComponent.java | 82 ++++++++ .../plan/gathering/NeoForgeSensor.java | 106 +++++++++++ .../properties/NeoForgeServerProperties.java | 45 +++++ .../NeoForgeServerPropertiesModule.java | 37 ++++ .../NeoForgeSuperClassBindingModule.java | 67 +++++++ .../plan/version/NeoForgeVersionChecker.java | 62 ++++++ .../plugin/NeoForgePlatformLayer.java | 72 +++++++ .../plugin/NeoForgePluginInformation.java | 50 +++++ .../resources/META-INF/neoforge.mods.toml | 38 ++++ .../plan/NeoForgeSystemTest.java | 69 +++++++ .../mocks/NeoForgeMockComponent.java | 82 ++++++++ .../utilities/mocks/PlanNeoForgeMocker.java | 57 ++++++ Plan/settings.gradle | 9 + README.md | 4 +- 22 files changed, 1176 insertions(+), 14 deletions(-) create mode 100644 Plan/neoforge/build.gradle create mode 100644 Plan/neoforge/src/main/java/net/playeranalytics/plan/PlanNeoForge.java create mode 100644 Plan/neoforge/src/main/java/net/playeranalytics/plan/PlanNeoForgeComponent.java create mode 100644 Plan/neoforge/src/main/java/net/playeranalytics/plan/gathering/NeoForgeSensor.java create mode 100644 Plan/neoforge/src/main/java/net/playeranalytics/plan/identification/properties/NeoForgeServerProperties.java create mode 100644 Plan/neoforge/src/main/java/net/playeranalytics/plan/modules/neoforge/NeoForgeServerPropertiesModule.java create mode 100644 Plan/neoforge/src/main/java/net/playeranalytics/plan/modules/neoforge/NeoForgeSuperClassBindingModule.java create mode 100644 Plan/neoforge/src/main/java/net/playeranalytics/plan/version/NeoForgeVersionChecker.java create mode 100644 Plan/neoforge/src/main/java/net/playeranalytics/plugin/NeoForgePlatformLayer.java create mode 100644 Plan/neoforge/src/main/java/net/playeranalytics/plugin/NeoForgePluginInformation.java create mode 100644 Plan/neoforge/src/main/resources/META-INF/neoforge.mods.toml create mode 100644 Plan/neoforge/src/test/java/net/playeranalytics/plan/NeoForgeSystemTest.java create mode 100644 Plan/neoforge/src/test/java/utilities/mocks/NeoForgeMockComponent.java create mode 100644 Plan/neoforge/src/test/java/utilities/mocks/PlanNeoForgeMocker.java diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b02566644..79a78a664b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,6 +82,12 @@ jobs: name: PlanFabric-${{ env.versionString }}-${{ env.git_hash }}.jar path: ${{ env.artifactPath }}/PlanFabric-${{ env.snapshotVersion }}.jar archive: false + - name: ๐Ÿ“ค Upload PlanNeoForge.jar + uses: actions/upload-artifact@v7 + with: + name: PlanNeoForge-${{ env.versionString }}-${{ env.git_hash }}.jar + path: ${{ env.artifactPath }}/PlanNeoForge-${{ env.snapshotVersion }}.jar + archive: false - name: ๐Ÿšฆ Setup Selenium Webdriver uses: nanasess/setup-chromedriver@v2 - name: ๐Ÿšฆ Setup Selenium Webdriver settings diff --git a/.github/workflows/on-release.yml b/.github/workflows/on-release.yml index adaeefc89f..481dccb733 100644 --- a/.github/workflows/on-release.yml +++ b/.github/workflows/on-release.yml @@ -13,7 +13,7 @@ jobs: uses: actions/checkout@v7 - name: Get Download URL run: | - curl 'https://api.github.com/repos/plan-player-analytics/plan/releases/${{ github.event.release.id }}/assets' | jq -r '.[] | {name: .name, url: .browser_download_url} | select(.url | strings | test("Fabric") | not)' > asset.txt + curl 'https://api.github.com/repos/plan-player-analytics/plan/releases/${{ github.event.release.id }}/assets' | jq -r '.[] | {name: .name, url: .browser_download_url} | select(.url | strings | test("Fabric|NeoForge") | not)' > asset.txt jq -r '.url' asset.txt > url.txt echo "RELEASE_DOWNLOAD_URL=$(cat url.txt)" >> $GITHUB_ENV - name: Write REL release line @@ -70,7 +70,7 @@ jobs: fetch-depth: 0 - name: Download release artifact for upload run: | - curl 'https://api.github.com/repos/plan-player-analytics/plan/releases/${{ github.event.release.id }}/assets' | jq -r '.[] | {name: .name, url: .browser_download_url} | select(.url | strings | test("Fabric") | not)' > asset.txt + curl 'https://api.github.com/repos/plan-player-analytics/plan/releases/${{ github.event.release.id }}/assets' | jq -r '.[] | {name: .name, url: .browser_download_url} | select(.url | strings | test("Fabric|NeoForge") | not)' > asset.txt jq -r '.url' asset.txt > url.txt jq -r '.name' asset.txt > name.txt wget -i url.txt @@ -102,7 +102,7 @@ jobs: fetch-depth: 0 - name: โš™ Get Download URL run: | - curl 'https://api.github.com/repos/plan-player-analytics/plan/releases/${{ github.event.release.id }}/assets' | jq -r '.[] | {name: .name, url: .browser_download_url} | select(.url | strings | test("Fabric") | not)' > asset.txt + curl 'https://api.github.com/repos/plan-player-analytics/plan/releases/${{ github.event.release.id }}/assets' | jq -r '.[] | {name: .name, url: .browser_download_url} | select(.url | strings | test("Fabric|NeoForge") | not)' > asset.txt jq -r '.url' asset.txt > url.txt echo "RELEASE_DOWNLOAD_URL=$(cat url.txt)" >> $GITHUB_ENV - name: โ˜• Setup JDK @@ -137,8 +137,8 @@ jobs: # Fetch all assets curl 'https://api.github.com/repos/plan-player-analytics/plan/releases/${{ github.event.release.id }}/assets' > all_assets.json - # Download regular JAR (non-Fabric) - jq -r '.[] | {name: .name, url: .browser_download_url} | select(.url | strings | test("Fabric") | not)' all_assets.json > asset_regular.txt + # Download regular JAR (non-Fabric / non-NeoForge) + jq -r '.[] | {name: .name, url: .browser_download_url} | select(.url | strings | test("Fabric|NeoForge") | not)' all_assets.json > asset_regular.txt jq -r '.url' asset_regular.txt > url_regular.txt jq -r '.name' asset_regular.txt > name_regular.txt wget -i url_regular.txt @@ -151,6 +151,13 @@ jobs: wget -i url_fabric.txt echo "FABRIC_JAR=$(cat name_fabric.txt)" >> $GITHUB_ENV + # Download NeoForge JAR + jq -r '.[] | {name: .name, url: .browser_download_url} | select(.url | strings | test("NeoForge"))' all_assets.json > asset_neoforge.txt + jq -r '.url' asset_neoforge.txt > url_neoforge.txt + jq -r '.name' asset_neoforge.txt > name_neoforge.txt + wget -i url_neoforge.txt + echo "NEOFORGE_JAR=$(cat name_neoforge.txt)" >> $GITHUB_ENV + - name: Modrinth Upload - Plugin ๐Ÿš€ uses: Kira-NT/mc-publish@v3 with: @@ -196,3 +203,24 @@ jobs: luckperms(optional){modrinth:Vebnzrzj}{curseforge:431733} fabric-permissions-api-v0(optional){modrinth:lzVo0Dll} java: 25 + + - name: Modrinth + Curseforge Upload - NeoForge ๐Ÿš€ + uses: Kira-NT/mc-publish@v3 + with: + modrinth-id: plan + modrinth-token: ${{ secrets.MODRINTH_TOKEN }} + curseforge-id: 508727 + curseforge-token: ${{ secrets.CF_API_TOKEN }} + files: ${{ env.NEOFORGE_JAR }} + name: ${{ github.event.release.name }} + version: ${{ env.FORMATTED_VERSION }}+neoforge + changelog: ${{ github.event.release.body }} + loaders: | + neoforge + game-versions: | + >=26.2 + version-type: ${{ github.event.release.prerelease && 'beta' || 'release' }} + dependencies: | + forgified-fabric-api(required){modrinth:Aqlf1Shp}{curseforge:889079} + luckperms(optional){modrinth:Vebnzrzj}{curseforge:431733} + java: 25 diff --git a/Plan/build.gradle b/Plan/build.gradle index fc1b6af7c2..d595c63787 100644 --- a/Plan/build.gradle +++ b/Plan/build.gradle @@ -12,6 +12,7 @@ plugins { id "org.sonarqube" version "7.3.1.8318" id "com.gradleup.shadow" version "9.6.1" apply false id "net.fabricmc.fabric-loom" version "1.17.17" apply false + id "net.neoforged.moddev" version "2.0.143" apply false } apply plugin: "nebula-aggregate-javadocs" @@ -105,6 +106,8 @@ subprojects { maven { url = "https://repo.playeranalytics.net/releases" } // Plan maven { url = "https://repo.md-5.net/content/repositories/snapshots/" } // RedisBungee maven { url = "https://jitpack.io" } // RedisBungee fork + maven { url = "https://maven.neoforged.net/releases" } // NeoForge + maven { url = "https://maven.sinytra.org" } // Forgified Fabric API } dependencies { diff --git a/Plan/fabric/src/main/java/net/playeranalytics/plan/commands/FabricCommandManager.java b/Plan/fabric/src/main/java/net/playeranalytics/plan/commands/FabricCommandManager.java index 42dd8f7c55..1895e02a32 100644 --- a/Plan/fabric/src/main/java/net/playeranalytics/plan/commands/FabricCommandManager.java +++ b/Plan/fabric/src/main/java/net/playeranalytics/plan/commands/FabricCommandManager.java @@ -35,7 +35,7 @@ import net.minecraft.commands.CommandSourceStack; import net.minecraft.network.chat.Component; import net.minecraft.server.permissions.Permissions; -import net.playeranalytics.plan.PlanFabric; +import com.djrapitops.plan.PlanPlugin; import net.playeranalytics.plugin.scheduling.RunnableFactory; import java.util.concurrent.CompletableFuture; @@ -45,10 +45,10 @@ public class FabricCommandManager { private final CommandDispatcher dispatcher; private RunnableFactory runnableFactory; private LiteralArgumentBuilder root; - private final PlanFabric plugin; + private final PlanPlugin plugin; private final ErrorLogger errorLogger; - public FabricCommandManager(CommandDispatcher dispatcher, PlanFabric plugin, ErrorLogger errorLogger) { + public FabricCommandManager(CommandDispatcher dispatcher, PlanPlugin plugin, ErrorLogger errorLogger) { this.dispatcher = dispatcher; this.plugin = plugin; this.errorLogger = errorLogger; diff --git a/Plan/fabric/src/main/java/net/playeranalytics/plan/gathering/listeners/FabricListenerSystem.java b/Plan/fabric/src/main/java/net/playeranalytics/plan/gathering/listeners/FabricListenerSystem.java index 70359975a9..6d8c5b0e7d 100644 --- a/Plan/fabric/src/main/java/net/playeranalytics/plan/gathering/listeners/FabricListenerSystem.java +++ b/Plan/fabric/src/main/java/net/playeranalytics/plan/gathering/listeners/FabricListenerSystem.java @@ -19,7 +19,6 @@ import com.djrapitops.plan.PlanPlugin; import com.djrapitops.plan.capability.CapabilitySvc; import com.djrapitops.plan.gathering.listeners.ListenerSystem; -import net.playeranalytics.plan.PlanFabric; import net.playeranalytics.plan.gathering.listeners.events.PlanFabricEvents; import net.playeranalytics.plan.gathering.listeners.fabric.*; import net.playeranalytics.plugin.server.Listeners; @@ -84,7 +83,7 @@ protected void unregisterListeners() { @Override public void callEnableEvent(PlanPlugin plugin) { boolean isEnabled = plugin.isSystemEnabled(); - PlanFabricEvents.ON_ENABLE.invoker().onEnable((PlanFabric) plugin); + PlanFabricEvents.ON_ENABLE.invoker().onEnable(plugin); CapabilitySvc.notifyAboutEnable(isEnabled); } } diff --git a/Plan/fabric/src/main/java/net/playeranalytics/plan/gathering/listeners/events/PlanFabricEvents.java b/Plan/fabric/src/main/java/net/playeranalytics/plan/gathering/listeners/events/PlanFabricEvents.java index 27f7066301..1d4d75227c 100644 --- a/Plan/fabric/src/main/java/net/playeranalytics/plan/gathering/listeners/events/PlanFabricEvents.java +++ b/Plan/fabric/src/main/java/net/playeranalytics/plan/gathering/listeners/events/PlanFabricEvents.java @@ -28,7 +28,7 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraft.network.chat.Component; import net.minecraft.world.level.GameType; -import net.playeranalytics.plan.PlanFabric; +import com.djrapitops.plan.PlanPlugin; import net.playeranalytics.plan.gathering.FabricPlayerPositionTracker; import java.net.SocketAddress; @@ -99,7 +99,7 @@ public class PlanFabricEvents { * *

* This event provides full access to the Plan instance. However, it is advised to - * only call {@link PlanFabric#isSystemEnabled} to determine if the enable was successful. + * only call {@link PlanPlugin#isSystemEnabled} to determine if the enable was successful. * It is not guaranteed that this event is called when the plugin fails to enable properly. */ public static final Event ON_ENABLE = EventFactory.createArrayBacked(OnEnable.class, callbacks -> plugin -> { @@ -188,6 +188,6 @@ public interface OnClientHandshake { @FunctionalInterface public interface OnEnable { - void onEnable(PlanFabric plugin); + void onEnable(PlanPlugin plugin); } } diff --git a/Plan/neoforge/build.gradle b/Plan/neoforge/build.gradle new file mode 100644 index 0000000000..efed72726d --- /dev/null +++ b/Plan/neoforge/build.gradle @@ -0,0 +1,168 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + +apply plugin: "net.neoforged.moddev" +apply plugin: "com.gradleup.shadow" + +ext { + neoVersion = "26.2.0.59" + minecraftVersion = "26.2" + // FFAPI has no 26.2 release yet; 26.1.2 is the stand-in until Sinytra ships 26.2. + ffapiVersion = "0.155.2+26.1.2+3.5.0" +} + +configurations { + shade + implementation.extendsFrom shade +} + +// Reuse Fabric platform code that is not Fabric-Loader specific. +sourceSets { + main { + java { + srcDir project(":fabric").file("src/main/java") + exclude "net/playeranalytics/plan/PlanFabric.java" + exclude "net/playeranalytics/plan/PlanFabricComponent.java" + exclude "net/playeranalytics/plugin/FabricPlatformLayer.java" + exclude "net/playeranalytics/plugin/FabricPluginInformation.java" + exclude "net/playeranalytics/plan/gathering/FabricSensor.java" + exclude "net/playeranalytics/plan/identification/properties/FabricServerProperties.java" + exclude "net/playeranalytics/plan/modules/fabric/FabricServerPropertiesModule.java" + exclude "net/playeranalytics/plan/modules/fabric/FabricSuperClassBindingModule.java" + exclude "net/playeranalytics/plan/version/FabricVersionChecker.java" + } + resources { + srcDir project(":fabric").file("src/main/resources") + exclude "fabric.mod.json" + } + } + test { + java { + srcDir project(":fabric").file("src/test/java") + exclude "net/playeranalytics/plan/FabricSystemTest.java" + exclude "utilities/mocks/FabricMockComponent.java" + exclude "utilities/mocks/PlanFabricMocker.java" + } + } +} + +neoForge { + version = neoVersion + + runs { + server { + server() + programArgument "--nogui" + } + configureEach { + logLevel = org.slf4j.event.Level.INFO + } + } + + mods { + plan { + sourceSet sourceSets.main + } + } +} + +dependencies { + shade project(":common") + shade project(path: ":extensions:adventure", configuration: "shadow") + + implementation "net.playeranalytics:platform-abstraction-layer-api:$palVersion" + + // Required at runtime; not shaded into PlanNeoForge.jar + implementation "org.sinytra.forgified-fabric-api:forgified-fabric-api:$ffapiVersion" + + // Optional permissions API (same package as Fabric); present when a NeoForge port is installed + compileOnly "me.lucko:fabric-permissions-api:0.7.0" + + testImplementation(testFixtures(project(":common"))) + testImplementation(project(":extensions:adventure")) +} + +compileJava { + options.release = 25 +} + +processResources { + inputs.property "version", fullVersionSemantic + inputs.property "neoVersion", neoVersion + inputs.property "minecraftVersion", minecraftVersion + + filesMatching("META-INF/neoforge.mods.toml") { + expand([ + "version" : fullVersionSemantic, + "neo_version" : neoVersion, + "minecraft_version": minecraftVersion + ]) + } +} + +tasks.named("shadowJar", ShadowJar) { + configurations = [project.configurations.shade] + + exclude "**/*.svg" + exclude "**/*.psd" + exclude "**/*.map" + exclude "LICENSE*.txt" + exclude "jetty-dir.css" + + exclude "**/module-info.class" + exclude "module-info.class" + exclude "META-INF/versions/" + exclude "mozilla/**/*" + + exclude "org/checkerframework/**/*" + exclude "com/google/common/**/*" + exclude "com/google/thirdparty/**/*" + exclude "com/google/auto/**/*" + + exclude "org.javassist:.*" + exclude "org/yaml/**/*" + exclude "nonapi/**/*" + exclude "io/github/classgraph/**/*" + exclude "io/swagger/**/*" + exclude "com/sun/activation/**/*" + exclude "jakarta/activation/**/*" + exclude "jakarta/validation/**/*" + exclude "jakarta/ws/**/*" + exclude "jakarta/xml/**/*" + exclude "javassist/**/*" + + // NeoForge 26+ JPMS validates META-INF/services; Adventure SPI entries reference + // classes that are relocated/absent after the adventure shadow jar is merged. + exclude "META-INF/services/net.kyori.**" + + relocate("org.apache", "plan.org.apache") { + exclude "org/apache/logging/**" + exclude "org/apache/maven/**" + } + + relocate "dagger", "plan.dagger" + relocate "javax.inject", "plan.javax.inject" + relocate "jakarta.inject", "plan.jakarta.inject" + + relocate "dev.vankka.dependencydownload", "plan.dev.vankka.dependencydownload" + relocate "com.github.benmanes", "plan.com.github.benmanes" + relocate "com.maxmind", "plan.com.maxmind" + relocate "com.fasterxml", "plan.com.fasterxml" + relocate "com.zaxxer", "plan.com.zaxxer" + relocate "com.google.gson", "plan.com.google.gson" + relocate "com.google.errorprone", "plan.com.google.errorprone" + relocate "org.bstats", "plan.org.bstats" + relocate "org.jasypt", "plan.org.jasypt" + relocate "org.json.simple", "plan.org.json.simple" + relocate "org.slf4j", "plan.org.slf4j" + + relocate "org.eclipse.jetty", "plan.org.eclipse.jetty" + relocate "jakarta.servlet", "plan.jakarta.servlet" + relocate "javax.servlet", "plan.javax.servlet" + + mergeServiceFiles() + + destinationDirectory.set(file("$rootDir/builds/")) + archiveFileName.set("PlanNeoForge-${project.version}.jar") +} + +tasks.assemble.dependsOn tasks.shadowJar diff --git a/Plan/neoforge/src/main/java/net/playeranalytics/plan/PlanNeoForge.java b/Plan/neoforge/src/main/java/net/playeranalytics/plan/PlanNeoForge.java new file mode 100644 index 0000000000..d770665d56 --- /dev/null +++ b/Plan/neoforge/src/main/java/net/playeranalytics/plan/PlanNeoForge.java @@ -0,0 +1,180 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package net.playeranalytics.plan; + +import com.djrapitops.plan.PlanPlugin; +import com.djrapitops.plan.PlanSystem; +import com.djrapitops.plan.commands.use.ColorScheme; +import com.djrapitops.plan.commands.use.Subcommand; +import com.djrapitops.plan.exceptions.EnableException; +import com.djrapitops.plan.gathering.ServerShutdownSave; +import com.djrapitops.plan.settings.locale.Locale; +import com.djrapitops.plan.settings.locale.lang.PluginLang; +import com.djrapitops.plan.settings.theme.PlanColorScheme; +import com.djrapitops.plan.utilities.java.ThreadContextClassLoaderSwap; +import com.djrapitops.plan.utilities.logging.ErrorLogger; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.minecraft.server.dedicated.DedicatedServer; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.common.Mod; +import net.neoforged.fml.loading.FMLPaths; +import net.playeranalytics.plan.commands.FabricCommandManager; +import net.playeranalytics.plan.identification.properties.NeoForgeServerProperties; +import net.playeranalytics.plugin.NeoForgePlatformLayer; +import net.playeranalytics.plugin.PlatformAbstractionLayer; +import net.playeranalytics.plugin.scheduling.RunnableFactory; +import net.playeranalytics.plugin.server.FabricPluginLogger; + +import java.io.File; +import java.io.InputStream; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Main class for Plan's NeoForge version (FFAPI). + */ +@Mod("plan") +public class PlanNeoForge implements PlanPlugin { + + private DedicatedServer server; + private FabricCommandManager fabricCommandManager; + + private PlanSystem system; + private Locale locale; + private ServerShutdownSave serverShutdownSave; + + private FabricPluginLogger pluginLogger; + private RunnableFactory runnableFactory; + private PlatformAbstractionLayer abstractionLayer; + private ErrorLogger errorLogger; + + public PlanNeoForge(IEventBus modEventBus) { + abstractionLayer = new NeoForgePlatformLayer(this); + pluginLogger = (FabricPluginLogger) abstractionLayer.getPluginLogger(); + runnableFactory = abstractionLayer.getRunnableFactory(); + + ServerLifecycleEvents.SERVER_STARTING.register(minecraftServer -> { + this.server = (DedicatedServer) minecraftServer; + onEnable(); + }); + + CommandRegistrationCallback.EVENT.register( + (dispatcher, registryAccess, environment) -> + fabricCommandManager = new FabricCommandManager(dispatcher, this, errorLogger) + ); + + ServerLifecycleEvents.SERVER_STOPPING.register(minecraftServer -> onDisable()); + } + + @Override + public InputStream getResource(String resource) { + return this.getClass().getResourceAsStream("/" + resource); + } + + @Override + public ColorScheme getColorScheme() { + return PlanColorScheme.create(system.getConfigSystem().getConfig(), pluginLogger); + } + + @Override + public PlanSystem getSystem() { + return system; + } + + @Override + public void registerCommand(Subcommand command) { + fabricCommandManager.registerRoot(command, runnableFactory); + } + + @Override + public void onEnable() { + PlanNeoForgeComponent component = DaggerPlanNeoForgeComponent.builder() + .plan(this) + .abstractionLayer(abstractionLayer) + .server(server) + .serverProperties(new NeoForgeServerProperties(getServer())) + .build(); + + try { + system = ThreadContextClassLoaderSwap.performOperation(getClass().getClassLoader(), component::system); + errorLogger = component.errorLogger(); + serverShutdownSave = component.serverShutdownSave(); + locale = system.getLocaleSystem().getLocale(); + registerCommand(component.planCommand().build()); + system.enable(); + + pluginLogger.info(locale.getString(PluginLang.ENABLED)); + } catch (AbstractMethodError e) { + pluginLogger.error("Plugin ran into AbstractMethodError, server restart is required! This error is likely caused by updating the JAR without a restart."); + } catch (EnableException e) { + pluginLogger.error("----------------------------------------"); + pluginLogger.error("Error: " + e.getMessage()); + pluginLogger.error("----------------------------------------"); + pluginLogger.error("Plugin failed to initialize correctly. If this issue is caused by config settings you can use /plan reload"); + onDisable(); + } catch (Exception e) { + String version = abstractionLayer.getPluginInformation().getVersion(); + pluginLogger.error(this.getClass().getSimpleName() + "-v" + version, e); + pluginLogger.error("Plugin Failed to Initialize Correctly. If this issue is caused by config settings you can use /plan reload"); + pluginLogger.error("This error should be reported at https://github.com/plan-player-analytics/Plan/issues"); + onDisable(); + } + if (system != null) { + system.getProcessing().submitNonCritical(() -> system.getListenerSystem().callEnableEvent(this)); + } + } + + @Override + public void onDisable() { + storeSessionsOnShutdown(); + runnableFactory.cancelAllKnownTasks(); + + if (system != null) system.disable(); + + pluginLogger.info(Locale.getStringNullSafe(locale, PluginLang.DISABLED)); + } + + private void storeSessionsOnShutdown() { + if (serverShutdownSave != null) { + Optional> complete = serverShutdownSave.performSave(); + if (complete.isPresent()) { + try { + complete.get().get(4, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (ExecutionException e) { + pluginLogger.error("Failed to save sessions to database on shutdown: " + e.getCause().getMessage()); + } catch (TimeoutException e) { + pluginLogger.info(Locale.getStringNullSafe(locale, PluginLang.DISABLED_UNSAVED_SESSIONS_TIMEOUT)); + } + } + } + } + + @Override + public File getDataFolder() { + return FMLPaths.CONFIGDIR.get().resolve("plan").toFile(); + } + + public DedicatedServer getServer() { + return server; + } +} diff --git a/Plan/neoforge/src/main/java/net/playeranalytics/plan/PlanNeoForgeComponent.java b/Plan/neoforge/src/main/java/net/playeranalytics/plan/PlanNeoForgeComponent.java new file mode 100644 index 0000000000..a5e885e539 --- /dev/null +++ b/Plan/neoforge/src/main/java/net/playeranalytics/plan/PlanNeoForgeComponent.java @@ -0,0 +1,82 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package net.playeranalytics.plan; + +import com.djrapitops.plan.PlanPlugin; +import com.djrapitops.plan.PlanSystem; +import com.djrapitops.plan.commands.PlanCommand; +import com.djrapitops.plan.delivery.rendering.json.datapoint.types.DatapointModule; +import com.djrapitops.plan.gathering.ServerShutdownSave; +import com.djrapitops.plan.modules.FiltersModule; +import com.djrapitops.plan.modules.PlatformAbstractionLayerModule; +import com.djrapitops.plan.modules.ServerCommandModule; +import com.djrapitops.plan.modules.SystemObjectProvidingModule; +import com.djrapitops.plan.utilities.logging.ErrorLogger; +import dagger.BindsInstance; +import dagger.Component; +import net.minecraft.server.dedicated.DedicatedServer; +import net.playeranalytics.plan.identification.properties.NeoForgeServerProperties; +import net.playeranalytics.plan.modules.fabric.FabricTaskModule; +import net.playeranalytics.plan.modules.neoforge.NeoForgeServerPropertiesModule; +import net.playeranalytics.plan.modules.neoforge.NeoForgeSuperClassBindingModule; +import net.playeranalytics.plugin.PlatformAbstractionLayer; + +import javax.inject.Singleton; + +/** + * Dagger component for constructing the required plugin systems on NeoForge. + */ +@Singleton +@Component(modules = { + SystemObjectProvidingModule.class, + PlatformAbstractionLayerModule.class, + FiltersModule.class, + DatapointModule.class, + + ServerCommandModule.class, + NeoForgeServerPropertiesModule.class, + NeoForgeSuperClassBindingModule.class, + FabricTaskModule.class +}) +public interface PlanNeoForgeComponent { + + PlanCommand planCommand(); + + PlanSystem system(); + + ServerShutdownSave serverShutdownSave(); + + ErrorLogger errorLogger(); + + @Component.Builder + interface Builder { + + @BindsInstance + Builder plan(PlanPlugin plan); + + @BindsInstance + Builder abstractionLayer(PlatformAbstractionLayer abstractionLayer); + + @BindsInstance + Builder server(DedicatedServer server); + + @BindsInstance + Builder serverProperties(NeoForgeServerProperties serverProperties); + + PlanNeoForgeComponent build(); + } +} diff --git a/Plan/neoforge/src/main/java/net/playeranalytics/plan/gathering/NeoForgeSensor.java b/Plan/neoforge/src/main/java/net/playeranalytics/plan/gathering/NeoForgeSensor.java new file mode 100644 index 0000000000..bd15eb67d5 --- /dev/null +++ b/Plan/neoforge/src/main/java/net/playeranalytics/plan/gathering/NeoForgeSensor.java @@ -0,0 +1,106 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package net.playeranalytics.plan.gathering; + +import com.djrapitops.plan.gathering.ServerSensor; +import com.djrapitops.plan.gathering.domain.PluginMetadata; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.neoforged.fml.ModList; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +@Singleton +public class NeoForgeSensor implements ServerSensor { + + private final DedicatedServer server; + + @Inject + public NeoForgeSensor(DedicatedServer server) { + this.server = server; + } + + @Override + public double getTPS() { + double totalTickLength = 0; + int count = 0; + for (long tickLength : server.getTickTimesNanos()) { + if (tickLength == 0) continue; + totalTickLength += Math.max(tickLength, TimeUnit.MILLISECONDS.toNanos(50)); + count++; + } + if (count == 0) { + return -1; + } else { + return TimeUnit.SECONDS.toNanos(1) / (totalTickLength / count); + } + } + + @Override + public Iterable getWorlds() { + return server.getAllLevels(); + } + + @Override + public int getEntityCount(ServerLevel world) { + int entities = 0; + for (Entity ignored : world.getAllEntities()) { + entities++; + } + return entities; + } + + @Override + public int getChunkCount(ServerLevel world) { + return world.getChunkSource().getLoadedChunksCount(); + } + + @Override + public boolean supportsDirectTPS() { + return true; + } + + @Override + public int getOnlinePlayerCount() { + return server.getPlayerCount(); + } + + @Override + public List getOnlinePlayerNames() { + return Arrays.asList(server.getPlayerNames()); + } + + @Override + public List getInstalledPlugins() { + return ModList.get().getMods().stream() + .map(info -> new PluginMetadata( + info.getDisplayName(), + info.getVersion().toString())) + .toList(); + } + + @Override + public Optional getMspt() { + return Optional.of(server.getTickTimesNanos()); + } +} diff --git a/Plan/neoforge/src/main/java/net/playeranalytics/plan/identification/properties/NeoForgeServerProperties.java b/Plan/neoforge/src/main/java/net/playeranalytics/plan/identification/properties/NeoForgeServerProperties.java new file mode 100644 index 0000000000..53712dd817 --- /dev/null +++ b/Plan/neoforge/src/main/java/net/playeranalytics/plan/identification/properties/NeoForgeServerProperties.java @@ -0,0 +1,45 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package net.playeranalytics.plan.identification.properties; + +import com.djrapitops.plan.identification.properties.ServerProperties; +import net.minecraft.server.dedicated.DedicatedServer; +import net.neoforged.fml.ModList; + +/** + * server.properties fetcher for NeoForge. + */ +public class NeoForgeServerProperties extends ServerProperties { + + public NeoForgeServerProperties(DedicatedServer server) { + super( + "NeoForge", + server.getPort(), + server.getServerVersion(), + ModList.get().getModContainerById("neoforge") + .map(container -> container.getModInfo().getVersion().toString()) + .orElse("Unknown") + + " (NeoForge), " + + ModList.get().getModContainerById("fabric_api") + .map(container -> container.getModInfo().getVersion().toString()) + .orElse("Unknown") + + " (FFAPI)", + () -> (server.getLocalIp() == null) ? "" : server.getLocalIp(), + server.getProperties().maxPlayers.get() + ); + } +} diff --git a/Plan/neoforge/src/main/java/net/playeranalytics/plan/modules/neoforge/NeoForgeServerPropertiesModule.java b/Plan/neoforge/src/main/java/net/playeranalytics/plan/modules/neoforge/NeoForgeServerPropertiesModule.java new file mode 100644 index 0000000000..763370c19c --- /dev/null +++ b/Plan/neoforge/src/main/java/net/playeranalytics/plan/modules/neoforge/NeoForgeServerPropertiesModule.java @@ -0,0 +1,37 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package net.playeranalytics.plan.modules.neoforge; + +import com.djrapitops.plan.identification.properties.ServerProperties; +import dagger.Module; +import dagger.Provides; +import net.playeranalytics.plan.identification.properties.NeoForgeServerProperties; + +import javax.inject.Singleton; + +/** + * Dagger module for providing ServerProperties on NeoForge. + */ +@Module +public class NeoForgeServerPropertiesModule { + + @Provides + @Singleton + ServerProperties provideServerProperties(NeoForgeServerProperties serverProperties) { + return serverProperties; + } +} diff --git a/Plan/neoforge/src/main/java/net/playeranalytics/plan/modules/neoforge/NeoForgeSuperClassBindingModule.java b/Plan/neoforge/src/main/java/net/playeranalytics/plan/modules/neoforge/NeoForgeSuperClassBindingModule.java new file mode 100644 index 0000000000..cf49d17728 --- /dev/null +++ b/Plan/neoforge/src/main/java/net/playeranalytics/plan/modules/neoforge/NeoForgeSuperClassBindingModule.java @@ -0,0 +1,67 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package net.playeranalytics.plan.modules.neoforge; + +import com.djrapitops.plan.gathering.ServerSensor; +import com.djrapitops.plan.gathering.ServerShutdownSave; +import com.djrapitops.plan.gathering.listeners.ListenerSystem; +import com.djrapitops.plan.identification.ServerInfo; +import com.djrapitops.plan.identification.ServerServerInfo; +import com.djrapitops.plan.settings.ConfigSystem; +import com.djrapitops.plan.settings.GameServerConfigSystem; +import com.djrapitops.plan.storage.database.DBSystem; +import com.djrapitops.plan.version.VersionChecker; +import dagger.Binds; +import dagger.Module; +import net.minecraft.server.level.ServerLevel; +import net.playeranalytics.plan.FabricServerShutdownSave; +import net.playeranalytics.plan.gathering.NeoForgeSensor; +import net.playeranalytics.plan.gathering.listeners.FabricListenerSystem; +import net.playeranalytics.plan.storage.database.FabricDBSystem; +import net.playeranalytics.plan.version.NeoForgeVersionChecker; + +/** + * Module for binding NeoForge-specific classes as interface implementations. + */ +@Module +public interface NeoForgeSuperClassBindingModule { + + @Binds + ServerInfo bindServerInfo(ServerServerInfo serverInfo); + + @Binds + DBSystem bindDBSystem(FabricDBSystem dbSystem); + + @Binds + ConfigSystem bindConfigSystem(GameServerConfigSystem configSystem); + + @Binds + ListenerSystem bindListenerSystem(FabricListenerSystem listenerSystem); + + @Binds + ServerShutdownSave bindServerShutdownSave(FabricServerShutdownSave shutdownSave); + + @Binds + ServerSensor bindServerSensor(NeoForgeSensor sensor); + + @Binds + @SuppressWarnings("java:S1452") + ServerSensor bindGenericsServerSensor(ServerSensor sensor); + + @Binds + VersionChecker bindVersionChecker(NeoForgeVersionChecker versionChecker); +} diff --git a/Plan/neoforge/src/main/java/net/playeranalytics/plan/version/NeoForgeVersionChecker.java b/Plan/neoforge/src/main/java/net/playeranalytics/plan/version/NeoForgeVersionChecker.java new file mode 100644 index 0000000000..9498a8ccd0 --- /dev/null +++ b/Plan/neoforge/src/main/java/net/playeranalytics/plan/version/NeoForgeVersionChecker.java @@ -0,0 +1,62 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package net.playeranalytics.plan.version; + +import com.djrapitops.plan.settings.config.PlanConfig; +import com.djrapitops.plan.settings.locale.Locale; +import com.djrapitops.plan.utilities.logging.ErrorLogger; +import com.djrapitops.plan.version.VersionChecker; +import com.djrapitops.plan.version.VersionInfo; +import net.playeranalytics.plugin.scheduling.RunnableFactory; +import net.playeranalytics.plugin.server.PluginLogger; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import java.util.Optional; + +/** + * System for checking if new Version is available when the System initializes, altering the link for NeoForge. + */ +@Singleton +public class NeoForgeVersionChecker extends VersionChecker { + @Inject + public NeoForgeVersionChecker( + @Named("currentVersion") String currentVersion, + Locale locale, + PlanConfig config, + PluginLogger logger, + RunnableFactory runnableFactory, + ErrorLogger errorLogger + ) { + super(currentVersion, locale, config, logger, runnableFactory, errorLogger); + } + + @Override + public Optional getNewVersionAvailable() { + if (newVersionAvailable == null) { + return Optional.empty(); + } else { + return Optional.of(new VersionInfo( + newVersionAvailable.isRelease(), + newVersionAvailable.getVersion(), + newVersionAvailable.getDownloadUrl().replace("Plan-", "PlanNeoForge-"), + newVersionAvailable.getChangeLogUrl() + )); + } + } +} diff --git a/Plan/neoforge/src/main/java/net/playeranalytics/plugin/NeoForgePlatformLayer.java b/Plan/neoforge/src/main/java/net/playeranalytics/plugin/NeoForgePlatformLayer.java new file mode 100644 index 0000000000..9b400265c4 --- /dev/null +++ b/Plan/neoforge/src/main/java/net/playeranalytics/plugin/NeoForgePlatformLayer.java @@ -0,0 +1,72 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package net.playeranalytics.plugin; + +import net.playeranalytics.plan.PlanNeoForge; +import net.playeranalytics.plugin.scheduling.FabricRunnableFactory; +import net.playeranalytics.plugin.scheduling.RunnableFactory; +import net.playeranalytics.plugin.server.FabricListeners; +import net.playeranalytics.plugin.server.FabricPluginLogger; +import net.playeranalytics.plugin.server.Listeners; +import net.playeranalytics.plugin.server.PluginLogger; +import org.apache.logging.log4j.LogManager; + +public class NeoForgePlatformLayer implements PlatformAbstractionLayer { + + private final PlanNeoForge plugin; + + private PluginLogger pluginLogger; + private Listeners listeners; + private PluginInformation pluginInformation; + private RunnableFactory runnableFactory; + + public NeoForgePlatformLayer(PlanNeoForge plugin) { + this.plugin = plugin; + } + + @Override + public PluginLogger getPluginLogger() { + if (pluginLogger == null) { + pluginLogger = new FabricPluginLogger(LogManager.getLogger("Plan")); + } + return pluginLogger; + } + + @Override + public Listeners getListeners() { + if (this.listeners == null) { + this.listeners = new FabricListeners(); + } + return listeners; + } + + @Override + public RunnableFactory getRunnableFactory() { + if (this.runnableFactory == null) { + this.runnableFactory = new FabricRunnableFactory(); + } + return runnableFactory; + } + + @Override + public PluginInformation getPluginInformation() { + if (this.pluginInformation == null) { + this.pluginInformation = new NeoForgePluginInformation(this.plugin); + } + return pluginInformation; + } +} diff --git a/Plan/neoforge/src/main/java/net/playeranalytics/plugin/NeoForgePluginInformation.java b/Plan/neoforge/src/main/java/net/playeranalytics/plugin/NeoForgePluginInformation.java new file mode 100644 index 0000000000..403c816536 --- /dev/null +++ b/Plan/neoforge/src/main/java/net/playeranalytics/plugin/NeoForgePluginInformation.java @@ -0,0 +1,50 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package net.playeranalytics.plugin; + +import net.neoforged.fml.ModList; +import net.playeranalytics.plan.PlanNeoForge; + +import java.io.File; +import java.io.InputStream; + +public class NeoForgePluginInformation implements PluginInformation { + + private final PlanNeoForge plugin; + + public NeoForgePluginInformation(PlanNeoForge plugin) { + this.plugin = plugin; + } + + @Override + public InputStream getResourceFromJar(String resource) { + return this.getClass().getResourceAsStream("/" + resource); + } + + @Override + public File getDataFolder() { + return plugin.getDataFolder(); + } + + @Override + public String getVersion() { + return ModList.get() + .getModContainerById("plan") + .map(container -> container.getModInfo().getVersion().toString()) + .orElse("unknown"); + } +} diff --git a/Plan/neoforge/src/main/resources/META-INF/neoforge.mods.toml b/Plan/neoforge/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 0000000000..db468623c6 --- /dev/null +++ b/Plan/neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,38 @@ +modLoader="javafml" +loaderVersion="[4,)" +license="LGPL-3.0-or-later" +issueTrackerURL="https://github.com/plan-player-analytics/Plan/issues" + +[[mods]] +modId="plan" +version="${version}" +displayName="Plan" +displayURL="https://github.com/plan-player-analytics/Plan" +authors="AuroraLS3, Player Analytics contributors" +description=''' +Player Analytics plugin for NeoForge (via Forgified Fabric API). +''' + +[[mixins]] +config="plan.mixins.json" + +[[dependencies.plan]] +modId="neoforge" +type="required" +versionRange="[${neo_version},)" +ordering="NONE" +side="BOTH" + +[[dependencies.plan]] +modId="minecraft" +type="required" +versionRange="[${minecraft_version}]" +ordering="NONE" +side="BOTH" + +[[dependencies.plan]] +modId="fabric_api" +type="required" +versionRange="*" +ordering="NONE" +side="BOTH" diff --git a/Plan/neoforge/src/test/java/net/playeranalytics/plan/NeoForgeSystemTest.java b/Plan/neoforge/src/test/java/net/playeranalytics/plan/NeoForgeSystemTest.java new file mode 100644 index 0000000000..edb795dcba --- /dev/null +++ b/Plan/neoforge/src/test/java/net/playeranalytics/plan/NeoForgeSystemTest.java @@ -0,0 +1,69 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package net.playeranalytics.plan; + +import com.djrapitops.plan.PlanSystem; +import com.djrapitops.plan.settings.config.PlanConfig; +import com.djrapitops.plan.settings.config.paths.WebserverSettings; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import utilities.RandomData; +import utilities.TestSettings; +import utilities.mocks.NeoForgeMockComponent; + +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test for NeoForge PlanSystem. + */ +class NeoForgeSystemTest { + + private static final int TEST_PORT_NUMBER = RandomData.randomInt(9005, 9500); + private PlanSystem system; + + @BeforeEach + void prepareSystem(@TempDir Path temp) { + system = new NeoForgeMockComponent(temp).getPlanSystem(); + system.getConfigSystem().getConfig() + .set(WebserverSettings.PORT, TEST_PORT_NUMBER); + } + + @Test + void neoForgeSystemEnables() { + try { + system.enable(); + assertTrue(system.isEnabled()); + } finally { + system.disable(); + } + } + + @Test + void neoForgeSystemHasDefaultConfigValuesAfterEnable() throws IllegalAccessException { + try { + system.enable(); + PlanConfig config = system.getConfigSystem().getConfig(); + + TestSettings.assertValidDefaultValuesForAllSettings(config, TestSettings.getServerSettings()); + } finally { + system.disable(); + } + } +} diff --git a/Plan/neoforge/src/test/java/utilities/mocks/NeoForgeMockComponent.java b/Plan/neoforge/src/test/java/utilities/mocks/NeoForgeMockComponent.java new file mode 100644 index 0000000000..68a741e5d4 --- /dev/null +++ b/Plan/neoforge/src/test/java/utilities/mocks/NeoForgeMockComponent.java @@ -0,0 +1,82 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package utilities.mocks; + +import com.djrapitops.plan.PlanPlugin; +import com.djrapitops.plan.PlanSystem; +import com.djrapitops.plan.storage.database.SQLDB; +import net.minecraft.server.dedicated.DedicatedServer; +import net.playeranalytics.plan.DaggerPlanNeoForgeComponent; +import net.playeranalytics.plan.PlanNeoForgeComponent; +import net.playeranalytics.plan.identification.properties.NeoForgeServerProperties; +import org.mockito.Mockito; + +import java.nio.file.Path; + +import static org.mockito.Mockito.doReturn; + +/** + * Test utility for creating a dagger PlanComponent using a mocked Plan on NeoForge. + */ +public class NeoForgeMockComponent { + + private final Path tempDir; + + private PlanPlugin planMock; + private PlanNeoForgeComponent component; + + public NeoForgeMockComponent(Path tempDir) { + this.tempDir = tempDir; + SQLDB.setDownloadDriver(false); + } + + public PlanPlugin getPlanMock() { + if (planMock == null) { + planMock = PlanNeoForgeMocker.setUp() + .withDataFolder(tempDir.resolve("data").toFile()) + .getPlanMock(); + } + return planMock; + } + + public PlanSystem getPlanSystem() { + if (component == null) { + PlanPlugin planMock = getPlanMock(); + component = DaggerPlanNeoForgeComponent.builder() + .plan(planMock) + .abstractionLayer(new TestPlatformAbstractionLayer(planMock)) + .server(mockServer()) + .serverProperties(mockServerProperties()) + .build(); + } + return component.system(); + } + + private DedicatedServer mockServer() { + DedicatedServer serverMock = Mockito.mock(DedicatedServer.class); + doReturn("").when(serverMock).getServerIp(); + doReturn(25565).when(serverMock).getPort(); + doReturn("26.2").when(serverMock).getServerVersion(); + return serverMock; + } + + private NeoForgeServerProperties mockServerProperties() { + NeoForgeServerProperties propertiesMock = Mockito.mock(NeoForgeServerProperties.class); + doReturn("").when(propertiesMock).getIp(); + return propertiesMock; + } +} diff --git a/Plan/neoforge/src/test/java/utilities/mocks/PlanNeoForgeMocker.java b/Plan/neoforge/src/test/java/utilities/mocks/PlanNeoForgeMocker.java new file mode 100644 index 0000000000..f316debb10 --- /dev/null +++ b/Plan/neoforge/src/test/java/utilities/mocks/PlanNeoForgeMocker.java @@ -0,0 +1,57 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package utilities.mocks; + +import com.djrapitops.plan.commands.use.ColorScheme; +import net.playeranalytics.plan.PlanNeoForge; +import org.mockito.Mockito; + +import java.io.File; + +import static org.mockito.Mockito.doReturn; + +/** + * Mocking utility for NeoForge version of Plan. + */ +public class PlanNeoForgeMocker { + + private PlanNeoForge planMock; + + private PlanNeoForgeMocker() { + } + + public static PlanNeoForgeMocker setUp() { + return new PlanNeoForgeMocker().mockPlugin(); + } + + private PlanNeoForgeMocker mockPlugin() { + planMock = Mockito.mock(PlanNeoForge.class); + + doReturn(new ColorScheme("ยง1", "ยง2", "ยง3")).when(planMock).getColorScheme(); + + return this; + } + + public PlanNeoForgeMocker withDataFolder(File tempFolder) { + doReturn(tempFolder).when(planMock).getDataFolder(); + return this; + } + + PlanNeoForge getPlanMock() { + return planMock; + } +} diff --git a/Plan/settings.gradle b/Plan/settings.gradle index 0470b2f1f0..9ae1435624 100644 --- a/Plan/settings.gradle +++ b/Plan/settings.gradle @@ -8,11 +8,19 @@ pluginManagement { name = "Sponge" url = "https://repo.spongepowered.org/repository/maven-public/" } + maven { + name = "NeoForged" + url = "https://maven.neoforged.net/releases" + } mavenCentral() gradlePluginPortal() } } +plugins { + id "org.gradle.toolchains.foojay-resolver-convention" version "1.0.0" +} + rootProject.name = "Plan" include "api" @@ -26,4 +34,5 @@ include "plugin" include "extensions" include "extensions:adventure" include "fabric" +include "neoforge" include "folia" diff --git a/README.md b/README.md index 32cd12ec03..9344a80056 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,9 @@ Player Analytics is a fully-fledged solution for the analytics needs of your Minecraft server. A built-in webserver displays insights into different aspects of the server such as Online activity, Playerbase, and how these change over time. -Plan supports multiple different platforms and versions; Spigot, Paper, Fabric, Sponge, SpongeForge, Bungeecord, Velocity, Folia & all derivatives - One jar for all platforms. +Plan supports multiple different platforms and versions; Spigot, Paper, Fabric, NeoForge (via Forgified Fabric API), Sponge, SpongeForge, Bungeecord, Velocity, Folia & all derivatives. + +Fabric and NeoForge each ship as separate jars (`PlanFabric-*.jar`, `PlanNeoForge-*.jar`). Other platforms share one jar. - [Version 5 Release trailer on Youtube](https://www.youtube.com/watch?v=BS_Ti9zkoRc)