From 9a8f27a4d5f84eb1a2bab63c49b9a48f5f127cde Mon Sep 17 00:00:00 2001 From: david Date: Wed, 8 Jul 2026 18:23:10 +0200 Subject: [PATCH 01/11] Use JIJ (jar-in-jar) over shading for modded platforms --- assets/faststats.png | Bin 0 -> 8740 bytes fabric/build.gradle.kts | 26 ++++++++++++++++-- fabric/example-mod/build.gradle.kts | 14 ++-------- fabric/src/main/resources/fabric.mod.json | 24 ++++++++++++++++ fabric/src/main/resources/faststats.png | 1 + .../versions/1.16.1-1.17.1/build.gradle.kts | 4 --- fabric/versions/1.18-1.21.8/build.gradle.kts | 4 --- .../versions/1.21.9-1.21.11/build.gradle.kts | 4 --- fabric/versions/26.1-26.3/build.gradle.kts | 4 --- neoforge/build.gradle.kts | 24 ++++++++++++++-- neoforge/example-mod/build.gradle.kts | 13 ++------- .../resources/META-INF/neoforge.mods.toml | 13 +++++++++ neoforge/src/main/resources/faststats.png | 1 + .../versions/1.20.6-1.21.8/build.gradle.kts | 11 -------- .../versions/1.21.9-1.21.11/build.gradle.kts | 11 -------- neoforge/versions/26.1-26.2/build.gradle.kts | 11 -------- 16 files changed, 91 insertions(+), 74 deletions(-) create mode 100644 assets/faststats.png create mode 100644 fabric/src/main/resources/fabric.mod.json create mode 120000 fabric/src/main/resources/faststats.png create mode 100644 neoforge/src/main/resources/META-INF/neoforge.mods.toml create mode 120000 neoforge/src/main/resources/faststats.png diff --git a/assets/faststats.png b/assets/faststats.png new file mode 100644 index 0000000000000000000000000000000000000000..bb5b3dae6168dcc9cc9e77afc79b41f2a10cac35 GIT binary patch literal 8740 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 literal 0 HcmV?d00001 diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index 4f6492cf..15f1de20 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -22,10 +22,32 @@ allprojects { extra.set("publishDocsUrl", "https://docs.faststats.dev/java/platform/fabric") } +subprojects { + if (project.name == "example-mod") return@subprojects + + val fabricJar = project(":fabric").tasks.named("jar") + + dependencies { + compileOnlyApi(project(":fabric")) + } + + tasks.jar { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + dependsOn(fabricJar) + from(fabricJar.flatMap { it.archiveFile }.map { it.asFile }) + } +} + dependencies { - api(project(":core")) - implementation(project(":config")) + compileOnlyApi(project(":core")) + compileOnly(project(":config")) minecraft("com.mojang:minecraft:26.1.2") compileOnly("net.fabricmc.fabric-api:fabric-api:0.150.0+26.1.2") compileOnly("net.fabricmc:fabric-loader:0.19.3") } + +tasks.jar { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + from(project(":config").sourceSets["main"].output) + from(project(":core").sourceSets["main"].output) +} diff --git a/fabric/example-mod/build.gradle.kts b/fabric/example-mod/build.gradle.kts index a75c01d5..a998da26 100644 --- a/fabric/example-mod/build.gradle.kts +++ b/fabric/example-mod/build.gradle.kts @@ -10,16 +10,8 @@ tasks.compileJava { } dependencies { - implementation(project(":fabric:versions:26.1-26.3")) - minecraft("com.mojang:minecraft:26.1.2") - implementation("net.fabricmc.fabric-api:fabric-api:0.150.0+26.1.2") compileOnly("net.fabricmc:fabric-loader:0.19.3") -} - -tasks.jar { - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - from(project(":config").sourceSets["main"].output) - from(project(":core").sourceSets["main"].output) - from(project(":fabric").sourceSets["main"].output) - from(project(":fabric:versions:26.1-26.3").sourceSets["main"].output) + compileOnly(project(":fabric:versions:26.1-26.3")) + include(project(":fabric:versions:26.1-26.3")) + minecraft("com.mojang:minecraft:26.2") } diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..f827eb97 --- /dev/null +++ b/fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,24 @@ +{ + "schemaVersion": 1, + "id": "faststats", + "version": "$version", + "name": "FastStats", + "description": "Analytics for developers", + "contact": { + "email": "mail@faststats.dev", + "homepage": "https://faststats.dev/mods", + "sources": "https://github.com/faststats-dev/faststats-java", + "issues": "https://github.com/faststats-dev/faststats-java/issues" + }, + "icon": "faststats.png", + "license": "MIT", + "authors": [ + "David | NonSwag" + ], + "environment": "*", + "depends": { + "fabricloader": ">=0.18.4", + "minecraft": "*", + "fabric-api": "*" + } +} diff --git a/fabric/src/main/resources/faststats.png b/fabric/src/main/resources/faststats.png new file mode 120000 index 00000000..0bdafe4f --- /dev/null +++ b/fabric/src/main/resources/faststats.png @@ -0,0 +1 @@ +/home/david/IdeaProjects/faststats-java/assets/faststats.png \ No newline at end of file diff --git a/fabric/versions/1.16.1-1.17.1/build.gradle.kts b/fabric/versions/1.16.1-1.17.1/build.gradle.kts index 3264ef9d..e07921c9 100644 --- a/fabric/versions/1.16.1-1.17.1/build.gradle.kts +++ b/fabric/versions/1.16.1-1.17.1/build.gradle.kts @@ -12,10 +12,6 @@ tasks.compileJava { } dependencies { - api(project(":fabric")) - api(project(":core")) - implementation(project(":config")) - // minecraft("com.mojang:minecraft:1.16.1") // minecraft("com.mojang:minecraft:1.16.2") // minecraft("com.mojang:minecraft:1.16.3") diff --git a/fabric/versions/1.18-1.21.8/build.gradle.kts b/fabric/versions/1.18-1.21.8/build.gradle.kts index bf1141de..9bb46a82 100644 --- a/fabric/versions/1.18-1.21.8/build.gradle.kts +++ b/fabric/versions/1.18-1.21.8/build.gradle.kts @@ -12,10 +12,6 @@ tasks.compileJava { } dependencies { - api(project(":fabric")) - api(project(":core")) - implementation(project(":config")) - // minecraft("com.mojang:minecraft:1.18") // minecraft("com.mojang:minecraft:1.18.1") // minecraft("com.mojang:minecraft:1.18.2") diff --git a/fabric/versions/1.21.9-1.21.11/build.gradle.kts b/fabric/versions/1.21.9-1.21.11/build.gradle.kts index e5dc199c..737433fc 100644 --- a/fabric/versions/1.21.9-1.21.11/build.gradle.kts +++ b/fabric/versions/1.21.9-1.21.11/build.gradle.kts @@ -12,10 +12,6 @@ tasks.compileJava { } dependencies { - api(project(":fabric")) - api(project(":core")) - implementation(project(":config")) - // minecraft("com.mojang:minecraft:1.21.9") // minecraft("com.mojang:minecraft:1.21.10") minecraft("com.mojang:minecraft:1.21.11") diff --git a/fabric/versions/26.1-26.3/build.gradle.kts b/fabric/versions/26.1-26.3/build.gradle.kts index 0d84d62e..b25ca735 100644 --- a/fabric/versions/26.1-26.3/build.gradle.kts +++ b/fabric/versions/26.1-26.3/build.gradle.kts @@ -12,10 +12,6 @@ tasks.compileJava { } dependencies { - api(project(":fabric")) - api(project(":core")) - implementation(project(":config")) - // minecraft("com.mojang:minecraft:26.1") // minecraft("com.mojang:minecraft:26.1.1") // minecraft("com.mojang:minecraft:26.1.2") diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts index 5171e50a..7acc5add 100644 --- a/neoforge/build.gradle.kts +++ b/neoforge/build.gradle.kts @@ -26,6 +26,20 @@ allprojects { extra.set("publishDocsUrl", "https://docs.faststats.dev/java/platform/neoforge") } +subprojects { + if (project.name == "example-mod") return@subprojects + + apply { plugin("net.neoforged.moddev") } + + pluginManager.withPlugin("net.neoforged.moddev") { + dependencies { + "jarJar"(project(":neoforge")) + compileOnly("net.neoforged:bus:8.0.5") + compileOnlyApi(project(":neoforge")) + } + } +} + neoForge { version = "21.8.53" // lowest bound, 1.20.6 } @@ -35,7 +49,13 @@ configurations.configureEach { } dependencies { - api(project(":core")) - implementation(project(":config")) + compileOnlyApi(project(":core")) + compileOnly(project(":config")) compileOnly("net.neoforged:bus:8.0.5") } + +tasks.jar { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + from(project(":config").sourceSets["main"].output) + from(project(":core").sourceSets["main"].output) +} diff --git a/neoforge/example-mod/build.gradle.kts b/neoforge/example-mod/build.gradle.kts index 653dcaee..7f59b6ac 100644 --- a/neoforge/example-mod/build.gradle.kts +++ b/neoforge/example-mod/build.gradle.kts @@ -1,6 +1,6 @@ plugins { id("net.neoforged.moddev") version "2.0.141" - kotlin("jvm") + //kotlin("jvm") } neoForge { @@ -12,13 +12,6 @@ configurations.configureEach { } dependencies { - implementation(project(":neoforge:versions:26.1-26.2")) -} - -tasks.jar { - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - from(project(":config").sourceSets["main"].output) - from(project(":core").sourceSets["main"].output) - from(project(":neoforge").sourceSets["main"].output) - from(project(":neoforge:versions:26.1-26.2").sourceSets["main"].output) + compileOnly(project(":neoforge:versions:26.1-26.2")) + jarJar(project(":neoforge:versions:26.1-26.2")) } 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 00000000..bfef8dde --- /dev/null +++ b/neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,13 @@ +modLoader = "javafml" +loaderVersion = "[1,)" +license = "MIT" +issueTrackerURL = "https://github.com/faststats-dev/faststats-java/issues" + +[[mods]] +modId = "faststats" +version = "${file.jarVersion}" +displayName = "FastStats" +description = "Analytics for developers" +logoFile = "faststats.png" +authors = "David | NonSwag" +displayURL = "https://faststats.dev/mods" diff --git a/neoforge/src/main/resources/faststats.png b/neoforge/src/main/resources/faststats.png new file mode 120000 index 00000000..0bdafe4f --- /dev/null +++ b/neoforge/src/main/resources/faststats.png @@ -0,0 +1 @@ +/home/david/IdeaProjects/faststats-java/assets/faststats.png \ No newline at end of file diff --git a/neoforge/versions/1.20.6-1.21.8/build.gradle.kts b/neoforge/versions/1.20.6-1.21.8/build.gradle.kts index 14ba7f9d..226fc57e 100644 --- a/neoforge/versions/1.20.6-1.21.8/build.gradle.kts +++ b/neoforge/versions/1.20.6-1.21.8/build.gradle.kts @@ -1,10 +1,6 @@ extra.set("moduleName", "dev.faststats.neoforge.compat.v1_20_6") extra.set("publishVersionSuffix", "mc1.21-1.21.11") -plugins { - id("net.neoforged.moddev") -} - java.toolchain.languageVersion = JavaLanguageVersion.of(25) tasks.compileJava { @@ -27,10 +23,3 @@ neoForge { configurations.configureEach { resolutionStrategy.force("com.google.code.gson:gson:2.14.0") } - -dependencies { - api(project(":neoforge")) - api(project(":core")) - implementation(project(":config")) - compileOnly("net.neoforged:bus:8.0.5") -} diff --git a/neoforge/versions/1.21.9-1.21.11/build.gradle.kts b/neoforge/versions/1.21.9-1.21.11/build.gradle.kts index f3008f6c..90c00f3f 100644 --- a/neoforge/versions/1.21.9-1.21.11/build.gradle.kts +++ b/neoforge/versions/1.21.9-1.21.11/build.gradle.kts @@ -1,10 +1,6 @@ extra.set("moduleName", "dev.faststats.neoforge.compat.v1_21_9") extra.set("publishVersionSuffix", "mc1.21.9-1.21.11") -plugins { - id("net.neoforged.moddev") -} - java.toolchain.languageVersion = JavaLanguageVersion.of(25) tasks.compileJava { @@ -20,10 +16,3 @@ neoForge { configurations.configureEach { resolutionStrategy.force("com.google.code.gson:gson:2.14.0") } - -dependencies { - api(project(":neoforge")) - api(project(":core")) - implementation(project(":config")) - compileOnly("net.neoforged:bus:8.0.5") -} diff --git a/neoforge/versions/26.1-26.2/build.gradle.kts b/neoforge/versions/26.1-26.2/build.gradle.kts index 99c764b6..33f383bf 100644 --- a/neoforge/versions/26.1-26.2/build.gradle.kts +++ b/neoforge/versions/26.1-26.2/build.gradle.kts @@ -1,10 +1,6 @@ extra.set("moduleName", "dev.faststats.neoforge.compat.v26_1") extra.set("publishVersionSuffix", "mc26.1-26.2") -plugins { - id("net.neoforged.moddev") -} - java.toolchain.languageVersion = JavaLanguageVersion.of(25) tasks.compileJava { @@ -21,10 +17,3 @@ neoForge { configurations.configureEach { resolutionStrategy.force("com.google.code.gson:gson:2.14.0") } - -dependencies { - api(project(":neoforge")) - api(project(":core")) - implementation(project(":config")) - compileOnly("net.neoforged:bus:8.0.5") -} From 18ac7d48d6fec5908fe3da1a7f99e06b3cb0c23e Mon Sep 17 00:00:00 2001 From: david Date: Wed, 8 Jul 2026 19:04:22 +0200 Subject: [PATCH 02/11] Replace absolute symlinks with relative paths --- fabric/src/main/resources/faststats.png | 2 +- neoforge/src/main/resources/faststats.png | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fabric/src/main/resources/faststats.png b/fabric/src/main/resources/faststats.png index 0bdafe4f..02b53612 120000 --- a/fabric/src/main/resources/faststats.png +++ b/fabric/src/main/resources/faststats.png @@ -1 +1 @@ -/home/david/IdeaProjects/faststats-java/assets/faststats.png \ No newline at end of file +../../../../assets/faststats.png \ No newline at end of file diff --git a/neoforge/src/main/resources/faststats.png b/neoforge/src/main/resources/faststats.png index 0bdafe4f..02b53612 120000 --- a/neoforge/src/main/resources/faststats.png +++ b/neoforge/src/main/resources/faststats.png @@ -1 +1 @@ -/home/david/IdeaProjects/faststats-java/assets/faststats.png \ No newline at end of file +../../../../assets/faststats.png \ No newline at end of file From 76ed05d2bb64641241db123e7b90cc08117e9535 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 8 Jul 2026 19:13:44 +0200 Subject: [PATCH 03/11] Do less sketchy things --- fabric/build.gradle.kts | 13 +++---------- .../1.16.1-1.17.1/src/main/java/module-info.java | 2 ++ .../1.18-1.21.8/src/main/java/module-info.java | 2 ++ .../1.21.9-1.21.11/src/main/java/module-info.java | 2 ++ .../26.1-26.3/src/main/java/module-info.java | 2 ++ 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index 15f1de20..a7f7f19d 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -25,16 +25,15 @@ allprojects { subprojects { if (project.name == "example-mod") return@subprojects - val fabricJar = project(":fabric").tasks.named("jar") - dependencies { compileOnlyApi(project(":fabric")) } tasks.jar { duplicatesStrategy = DuplicatesStrategy.EXCLUDE - dependsOn(fabricJar) - from(fabricJar.flatMap { it.archiveFile }.map { it.asFile }) + from(project(":fabric").sourceSets["main"].output) + from(project(":config").sourceSets["main"].output) + from(project(":core").sourceSets["main"].output) } } @@ -45,9 +44,3 @@ dependencies { compileOnly("net.fabricmc.fabric-api:fabric-api:0.150.0+26.1.2") compileOnly("net.fabricmc:fabric-loader:0.19.3") } - -tasks.jar { - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - from(project(":config").sourceSets["main"].output) - from(project(":core").sourceSets["main"].output) -} diff --git a/fabric/versions/1.16.1-1.17.1/src/main/java/module-info.java b/fabric/versions/1.16.1-1.17.1/src/main/java/module-info.java index 445dd1f2..2f4b4325 100644 --- a/fabric/versions/1.16.1-1.17.1/src/main/java/module-info.java +++ b/fabric/versions/1.16.1-1.17.1/src/main/java/module-info.java @@ -3,6 +3,8 @@ @NullMarked module dev.faststats.fabric.compat.v1_16_1 { + exports dev.faststats.fabric.compat.v1_16_1; + requires dev.faststats.fabric; requires static org.jspecify; diff --git a/fabric/versions/1.18-1.21.8/src/main/java/module-info.java b/fabric/versions/1.18-1.21.8/src/main/java/module-info.java index 58b4c535..ae8e4213 100644 --- a/fabric/versions/1.18-1.21.8/src/main/java/module-info.java +++ b/fabric/versions/1.18-1.21.8/src/main/java/module-info.java @@ -3,6 +3,8 @@ @NullMarked module dev.faststats.fabric.compat.v1_18 { + exports dev.faststats.fabric.compat.v1_18; + requires dev.faststats.fabric; requires static org.jspecify; diff --git a/fabric/versions/1.21.9-1.21.11/src/main/java/module-info.java b/fabric/versions/1.21.9-1.21.11/src/main/java/module-info.java index d3e8802b..52d89f7e 100644 --- a/fabric/versions/1.21.9-1.21.11/src/main/java/module-info.java +++ b/fabric/versions/1.21.9-1.21.11/src/main/java/module-info.java @@ -3,6 +3,8 @@ @NullMarked module dev.faststats.fabric.compat.v1_21_9 { + exports dev.faststats.fabric.compat.v1_21_9; + requires dev.faststats.fabric; requires static org.jspecify; diff --git a/fabric/versions/26.1-26.3/src/main/java/module-info.java b/fabric/versions/26.1-26.3/src/main/java/module-info.java index 4336e589..a57d0d4d 100644 --- a/fabric/versions/26.1-26.3/src/main/java/module-info.java +++ b/fabric/versions/26.1-26.3/src/main/java/module-info.java @@ -3,6 +3,8 @@ @NullMarked module dev.faststats.fabric.compat.v26_1 { + exports dev.faststats.fabric.compat.v26_1; + requires dev.faststats.fabric; requires static org.jspecify; From a3dfc8c405c7556206dc60bdd2eadf192c7b50a8 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 8 Jul 2026 22:27:16 +0200 Subject: [PATCH 04/11] Add initial onboarding screen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I don't know, it just happened, now we are stuck with this mess :…( --- .../com/example/mixin/OnboardingScreen.java | 465 ++++++++++++++++++ .../main/resources/example-mod.mixins.json | 11 + .../src/main/resources/fabric.mod.json | 3 + 3 files changed, 479 insertions(+) create mode 100644 fabric/example-mod/src/main/java/com/example/mixin/OnboardingScreen.java create mode 100644 fabric/example-mod/src/main/resources/example-mod.mixins.json diff --git a/fabric/example-mod/src/main/java/com/example/mixin/OnboardingScreen.java b/fabric/example-mod/src/main/java/com/example/mixin/OnboardingScreen.java new file mode 100644 index 00000000..1f9874d1 --- /dev/null +++ b/fabric/example-mod/src/main/java/com/example/mixin/OnboardingScreen.java @@ -0,0 +1,465 @@ +package com.example.mixin; + +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.Checkbox; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.TitleScreen; +import net.minecraft.client.input.MouseButtonEvent; +import net.minecraft.network.chat.ClickEvent; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.FormattedText; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.Style; +import net.minecraft.network.chat.TextColor; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.net.URI; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +@NullMarked +@Mixin(TitleScreen.class) +public abstract class OnboardingScreen { + @Unique + private static boolean dismissed; + + @Inject(method = "init", at = @At(value = "TAIL")) + private void showFastStatsOnboardingScreen(final CallbackInfo cir) { + if (!dismissed) { + Minecraft.getInstance().setScreenAndShow(new Onboarding()); + } + } + + // todo: add a button to open the onboarding screen somewhere in the settings + + private static final class Onboarding extends Screen { + private static final String MODS_URL = "https://faststats.dev/mods"; + private static final String ABUSE_URL = "https://faststats.dev/abuse"; + private static final String INFO_URL = "https://faststats.dev/info"; + private static final Component title = Component.literal("FastStats Metrics"); + private static final Component description = Component.literal("") + .append(link("FastStats", MODS_URL)) + .append(" collects anonymous usage statistics and errors.\n") + .append("Keeping Metrics and Error tracking enabled helps developers to improve their mods.\n\n") + .append("If you suspect a developer is collecting personal data or bypassing any opt-out option,\n") + .append("please report it at: ") + .append(link(ABUSE_URL, ABUSE_URL)); + + private static final Component submitMetrics = Component.literal("Submit Metrics"); + private static final Component submitAdditionalMetrics = Component.literal("Submit Additional Metrics (provided by the developer)"); + private static final Component errorTracking = Component.literal("Submit Errors"); + + private static final String[] alwaysCollectedData = new String[]{ + "Mod version", + "Fabric version", + "..." + }; + + private static final Set expandedDetails = new HashSet<>(); + + private static final Set mods = Set.of( + new Mod("TreeHugger69", new String[]{"Default Metrics", "Error Tracking"}, new String[]{"client_age", "language"}), + new Mod("Sucker123", new String[]{"Default Metrics"}, new String[]{}) + ); + + private record Mod(String name, String[] metrics, String[] additionalMetrics) { + + } + + private boolean collectedDataExpanded; + private boolean installedModsExpanded; + private boolean submitMetricsSelected = true; + private boolean submitAdditionalMetricsSelected = true; + private boolean errorTrackingSelected = true; + private int scrollOffset; + + private Checkbox submitMetricsWidget; + private Checkbox submitAdditionalMetricsWidget; + private Checkbox errorTrackingWidget; + private Button declineButton; + private Button acceptButton; + + private Onboarding() { + super(title); + } + + @Override + protected void init() { + this.scrollOffset = Math.min(this.scrollOffset, this.maxScrollOffset()); + + this.submitMetricsWidget = this.addRenderableWidget(Checkbox.builder(Onboarding.submitMetrics, this.font) + .pos(contentLeft(), checkboxY()) + .onValueChange((checkbox, selected) -> { + this.submitMetricsSelected = selected; + this.rebuildWidgets(); + }) + .selected(this.submitMetricsSelected) + .build()); + this.submitAdditionalMetricsWidget = this.addRenderableWidget(Checkbox.builder(this.additionalMetricsLabel(), this.font) + .pos(contentLeft(), checkboxY() + CHECKBOX_GAP) + .onValueChange((checkbox, selected) -> this.submitAdditionalMetricsSelected = selected) + .selected(this.submitAdditionalMetricsSelected && this.submitMetricsSelected) + .build()); + this.submitAdditionalMetricsWidget.active = this.submitMetricsSelected; + this.submitAdditionalMetricsWidget.setAlpha(this.submitMetricsSelected ? 1.0F : 0.45F); + this.errorTrackingWidget = this.addRenderableWidget(Checkbox.builder(Onboarding.errorTracking, this.font) + .pos(contentLeft(), checkboxY() + CHECKBOX_GAP * 2) + .onValueChange((checkbox, selected) -> this.errorTrackingSelected = selected) + .selected(this.errorTrackingSelected) + .build()); + + final var buttonGap = 2; + final var buttonWidth = (contentWidth() - buttonGap) / 2; + this.declineButton = this.addRenderableWidget(Button.builder(Component.literal("Decline All").withColor(TextColor.RED), button -> { + // todo: disable all… close immediately or let the user also click "confirm"? + this.onClose(); + }).bounds(contentLeft(), buttonY(), buttonWidth, 20).build()); + this.acceptButton = this.addRenderableWidget(Button.builder(Component.literal("Confirm Selection").withColor(TextColor.GREEN), button -> { + // todo: save selection + this.onClose(); + }).bounds(contentLeft() + buttonWidth + buttonGap, buttonY(), buttonWidth, 20).build()); + this.updateWidgetPositions(); + } + + @Override + public void onClose() { + dismissed = true; + Minecraft.getInstance().setScreenAndShow(new TitleScreen()); + } + + // fixme: this is hellish code, i bet i will forget what it does until tomorrow :) + @Override + public void extractRenderState(final GuiGraphicsExtractor graphics, final int mouseX, final int mouseY, final float a) { + super.extractRenderState(graphics, mouseX, mouseY, a); + + final int panelWidth = Math.min(this.width - 32, 1186); + final int panelHeight = this.height - 54; + final int panelX = (this.width - panelWidth) / 2; + final int panelY = 27; + final int border = 0xFFFFFFFF; + final int text = 0xFFE0E0E0; + + graphics.outline(panelX, panelY, panelWidth, panelHeight, border); + graphics.centeredText(this.font, title, this.width / 2, panelY + 45, text); + + final int contentInset = contentInset(panelWidth); + final int descriptionX = panelX + contentInset; + final int descriptionY = descriptionY(); + final int descriptionWidth = panelWidth - contentInset * 2; + final int descriptionHeight = descriptionHeight(); + graphics.enableScissor(panelX + 1, scrollTop(), panelX + panelWidth - 1, scrollBottom()); + graphics.fill(descriptionX + 1, descriptionY + 1, descriptionX + descriptionWidth - 1, descriptionY + descriptionHeight - 1, 0xFF000000); + graphics.outline(descriptionX, descriptionY, descriptionWidth, descriptionHeight, border); + graphics.textWithWordWrap(this.font, description, descriptionX + 24, descriptionY + 16, descriptionWidth - 48, text, false); + + final int infoY = descriptionY + descriptionHeight + 12; + final int infoHeight = infoHeight(); + graphics.fill(descriptionX + 1, infoY + 1, descriptionX + descriptionWidth - 1, infoY + infoHeight - 1, 0xFF000000); + graphics.outline(descriptionX, infoY, descriptionWidth, infoHeight, border); + graphics.textWithWordWrap(this.font, infoDescription(), descriptionX + 24, infoY + 16, descriptionWidth - 48, text, false); + graphics.disableScissor(); + this.renderScrollbar(graphics, panelX, panelY, panelWidth, panelHeight, border); + } + + // fixme: all of this sucks + @Override + public boolean mouseClicked(final MouseButtonEvent event, final boolean doubleClick) { + final Style clickedStyle = this.getStyleAt(description, event.x(), event.y(), descriptionTextX(), descriptionTextY(), descriptionTextWidth()); + if (clickedStyle != null && clickedStyle.getClickEvent() != null) { + defaultHandleClickEvent(clickedStyle.getClickEvent(), this.minecraft, this); + return true; + } + + if (event.y() < scrollTop() || event.y() > scrollBottom()) { + return super.mouseClicked(event, doubleClick); + } + + final Component info = infoDescription(); + final Style clickedInfoStyle = this.getStyleAt(info, event.x(), event.y(), infoTextX(), infoTextY(), descriptionTextWidth()); + if (clickedInfoStyle != null && clickedInfoStyle.getClickEvent() != null) { + defaultHandleClickEvent(clickedInfoStyle.getClickEvent(), this.minecraft, this); + return true; + } + + final String clickedInfoLine = this.getLineAt(info, event.x(), event.y(), infoTextX(), infoTextY(), descriptionTextWidth()); + if (clickedInfoLine != null) { + if (clickedInfoLine.contains("What data is collected?")) { + this.collectedDataExpanded = !this.collectedDataExpanded; + this.updateWidgetPositions(); + return true; + } + if (clickedInfoLine.contains("List of installed mods that use FastStats")) { + this.installedModsExpanded = !this.installedModsExpanded; + this.updateWidgetPositions(); + return true; + } + if (clickedInfoLine.contains("Additional Metrics")) { + expandedDetails.add(""); // todo: idk how to expand that crap + this.updateWidgetPositions(); + return true; + } + } + + return super.mouseClicked(event, doubleClick); + } + + @Override + public boolean mouseScrolled(final double mouseX, final double mouseY, final double horizontalAmount, final double verticalAmount) { + final int maxScrollOffset = this.maxScrollOffset(); + if (maxScrollOffset <= 0) { + return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount); + } + + final var b = this.scrollOffset - (int) (verticalAmount * 20); + this.scrollOffset = Math.clamp(b, 0, maxScrollOffset); + this.updateWidgetPositions(); + return true; + } + + // fixme: hacky bs + private @Nullable Style getStyleAt(final Component component, final double mouseX, final double mouseY, final int textX, final int textY, final int textWidth) { + final var line = this.getFormattedLineAt(component, mouseX, mouseY, textX, textY, textWidth); + if (line == null) { + return null; + } + + final var relativeX = mouseX - textX; + final var currentX = new double[]{0}; + final var style = line.visit((segmentStyle, text) -> { + final float segmentWidth = this.font.getSplitter().stringWidth(FormattedText.of(text, segmentStyle)); + if (relativeX >= currentX[0] && relativeX <= currentX[0] + segmentWidth) { + return Optional.of(segmentStyle); + } + + currentX[0] += segmentWidth; + return Optional.empty(); + }, Style.EMPTY); + + return style.orElse(null); + } + + private @Nullable String getLineAt(final Component component, final double mouseX, final double mouseY, final int textX, final int textY, final int textWidth) { + final FormattedText line = this.getFormattedLineAt(component, mouseX, mouseY, textX, textY, textWidth); + return line == null ? null : line.getString(); + } + + private @Nullable FormattedText getFormattedLineAt(final Component component, final double mouseX, final double mouseY, final int textX, final int textY, final int textWidth) { + if (mouseX < textX || mouseX > textX + textWidth || mouseY < textY) { + return null; + } + + final List lines = this.font.getSplitter().splitLines(component, textWidth, Style.EMPTY); + final int line = (int) ((mouseY - textY) / this.font.lineHeight); + if (line < 0 || line >= lines.size()) { + return null; + } + + return lines.get(line); + } + + private int descriptionTextX() { + final int panelWidth = Math.min(this.width - 32, 1186); + final int panelX = (this.width - panelWidth) / 2; + final int contentInset = contentInset(panelWidth); + return panelX + contentInset + 24; + } + + private int descriptionTextY() { + return descriptionY() + 16; + } + + private int infoTextX() { + return descriptionTextX(); + } + + private int infoTextY() { + return infoY() + 16; + } + + private int contentLeft() { + final int panelWidth = Math.min(this.width - 32, 1186); + final int panelX = (this.width - panelWidth) / 2; + final int contentInset = contentInset(panelWidth); + return panelX + contentInset + 2; + } + + private int contentWidth() { + final int panelWidth = Math.min(this.width - 32, 1186); + final int contentInset = contentInset(panelWidth); + return panelWidth - contentInset * 2 - 4; + } + + // fixme: magic numbers here we go!!! all of this is bound to break :) + private static final int CHECKBOX_GAP = 30; + + private int descriptionY() { + return 27 + 122 - this.scrollOffset; + } + + private int baseInfoY() { + return 27 + 122 + descriptionHeight() + 12 - this.scrollOffset; + } + + private int infoY() { + return this.baseInfoY(); + } + + private int scrollTop() { + return 27 + 88; + } + + private int scrollBottom() { + return this.height - 55; + } + + private int checkboxY() { + return this.infoY() + infoHeight() + 22; + } + + private int buttonY() { + return checkboxY() + CHECKBOX_GAP * 2 + 20 + 16; + } + + private int contentBottom() { + return 27 + 122 + descriptionHeight() + 12 + infoHeight() + 22 + CHECKBOX_GAP * 2 + 20 + 16 + 20; + } + + private int maxScrollOffset() { + return Math.max(0, contentBottom() - scrollBottom()); + } + + private void updateWidgetPositions() { + this.scrollOffset = Math.min(this.scrollOffset, this.maxScrollOffset()); + + final int buttonGap = 2; + final int buttonWidth = (contentWidth() - buttonGap) / 2; + this.submitMetricsWidget.setY(checkboxY()); + this.submitAdditionalMetricsWidget.setY(checkboxY() + CHECKBOX_GAP); + this.errorTrackingWidget.setY(checkboxY() + CHECKBOX_GAP * 2); + this.submitMetricsWidget.visible = isInsideScrollArea(this.submitMetricsWidget.getY(), this.submitMetricsWidget.getHeight()); + this.submitAdditionalMetricsWidget.visible = isInsideScrollArea(this.submitAdditionalMetricsWidget.getY(), this.submitAdditionalMetricsWidget.getHeight()); + this.errorTrackingWidget.visible = isInsideScrollArea(this.errorTrackingWidget.getY(), this.errorTrackingWidget.getHeight()); + this.declineButton.setX(contentLeft()); + this.declineButton.setY(buttonY()); + this.declineButton.setWidth(buttonWidth); + this.declineButton.setHeight(20); + this.acceptButton.setX(contentLeft() + buttonWidth + buttonGap); + this.acceptButton.setY(buttonY()); + this.acceptButton.setWidth(buttonWidth); + this.acceptButton.setHeight(20); + this.declineButton.visible = isInsideScrollArea(this.declineButton.getY(), this.declineButton.getHeight()); + this.acceptButton.visible = isInsideScrollArea(this.acceptButton.getY(), this.acceptButton.getHeight()); + } + + private Component additionalMetricsLabel() { + if (this.submitMetricsSelected) { + return Onboarding.submitAdditionalMetrics; + } + + return Onboarding.submitAdditionalMetrics.copy().withStyle(ChatFormatting.GRAY); + } + + private boolean isInsideScrollArea(final int y, final int height) { + return y + height > scrollTop() && y < scrollBottom(); + } + + // fixme: there has to be some inbuilt way to do this, right? RIGHT??? + private void renderScrollbar(final GuiGraphicsExtractor graphics, final int panelX, final int panelY, final int panelWidth, final int panelHeight, final int color) { + final int maxScrollOffset = this.maxScrollOffset(); + if (maxScrollOffset <= 0) { + return; + } + + final int trackX = panelX + panelWidth - 10; + final int trackY = scrollTop(); + final int trackHeight = scrollBottom() - scrollTop(); + final int contentHeight = contentBottom() - scrollTop(); + final int thumbHeight = Math.clamp((long) trackHeight * trackHeight / contentHeight, 18, trackHeight); + final int thumbY = trackY + this.scrollOffset * (trackHeight - thumbHeight) / maxScrollOffset; + graphics.fill(trackX, trackY, trackX + 2, trackY + trackHeight, 0x55FFFFFF); + graphics.fill(trackX - 1, thumbY, trackX + 3, thumbY + thumbHeight, color); + } + + private int descriptionTextWidth() { + final int panelWidth = Math.min(this.width - 32, 1186); + final int contentInset = contentInset(panelWidth); + final int descriptionWidth = panelWidth - contentInset * 2; + return descriptionWidth - 48; + } + + private int descriptionHeight() { + return Math.max(76, this.height / 5); + } + + private int infoHeight() { + return Math.max(60, this.font.wordWrapHeight(infoDescription(), descriptionTextWidth()) + 32); + } + + // todo: maybe an object that keeps track of that? collapsed text and everything… + private Component infoDescription() { + final MutableComponent component = Component.literal("") + .append(collapsible("What data is collected?", this.collectedDataExpanded)); + if (this.collectedDataExpanded) { + for (final var item : alwaysCollectedData) { + component.append("\n· ").append(item); + } + component.append("\nRead more here: ") + .append(link(INFO_URL, INFO_URL)); + } + + component.append("\n\n") + .append(collapsible("List of installed mods that use FastStats", this.installedModsExpanded)); + if (this.installedModsExpanded) mods.forEach(mod -> { + component.append("\n· ").append(mod.name()); + + for (final var metric : mod.metrics()) { + component.append("\n ").append(metric); + } + + final var length = mod.additionalMetrics().length; + if (length == 0) return; + + final boolean expanded = expandedDetails.contains(mod.name() + "-additional"); // todo: still no toggle + final var collapsible = collapsible("Additional Metrics (" + length + ")", expanded); + component.append("\n ").append(collapsible); + if (expanded) for (final var metric : mod.additionalMetrics()) { + component.append("\n · ").append(metric); + } + }); + + return component; + } + + private static int contentInset(final int panelWidth) { + return Math.clamp(panelWidth / 8, 24, 160); + } + + private static Component collapsible(final String text, final boolean expanded) { + return Component.literal((expanded ? "˅ " : "˃ ") + text) + .withStyle(ChatFormatting.YELLOW); + } + + private static Component link(final String text, final String url) { + return Component.literal(text).withStyle(style -> style + .withClickEvent(new ClickEvent.OpenUrl(URI.create(url))) + .withColor(ChatFormatting.AQUA) + .withUnderlined(true)); + } + + @Override + public boolean shouldCloseOnEsc() { + return false; + } + } +} diff --git a/fabric/example-mod/src/main/resources/example-mod.mixins.json b/fabric/example-mod/src/main/resources/example-mod.mixins.json new file mode 100644 index 00000000..d1e78105 --- /dev/null +++ b/fabric/example-mod/src/main/resources/example-mod.mixins.json @@ -0,0 +1,11 @@ +{ + "required": true, + "package": "com.example.mixin", + "compatibilityLevel": "JAVA_25", + "client": [ + "OnboardingScreen" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/fabric/example-mod/src/main/resources/fabric.mod.json b/fabric/example-mod/src/main/resources/fabric.mod.json index fa32a0e8..c71287d3 100644 --- a/fabric/example-mod/src/main/resources/fabric.mod.json +++ b/fabric/example-mod/src/main/resources/fabric.mod.json @@ -12,6 +12,9 @@ "com.example.ExampleMod" ] }, + "mixins": [ + "example-mod.mixins.json" + ], "depends": { "fabricloader": ">=0.18.4", "minecraft": "*", From 553bdf3993b5c7ad06c6e67f2a567b505b9a3768 Mon Sep 17 00:00:00 2001 From: david Date: Fri, 10 Jul 2026 18:57:04 +0200 Subject: [PATCH 05/11] Correct java version --- fabric/versions/1.21.9-1.21.11/build.gradle.kts | 4 ++-- neoforge/versions/1.20.6-1.21.8/build.gradle.kts | 4 ++-- neoforge/versions/1.21.9-1.21.11/build.gradle.kts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/fabric/versions/1.21.9-1.21.11/build.gradle.kts b/fabric/versions/1.21.9-1.21.11/build.gradle.kts index 737433fc..42926dc8 100644 --- a/fabric/versions/1.21.9-1.21.11/build.gradle.kts +++ b/fabric/versions/1.21.9-1.21.11/build.gradle.kts @@ -5,10 +5,10 @@ plugins { id("net.fabricmc.fabric-loom-remap") } -java.toolchain.languageVersion = JavaLanguageVersion.of(25) +java.toolchain.languageVersion = JavaLanguageVersion.of(21) tasks.compileJava { - options.release.set(25) + options.release.set(21) } dependencies { diff --git a/neoforge/versions/1.20.6-1.21.8/build.gradle.kts b/neoforge/versions/1.20.6-1.21.8/build.gradle.kts index 226fc57e..a60a502b 100644 --- a/neoforge/versions/1.20.6-1.21.8/build.gradle.kts +++ b/neoforge/versions/1.20.6-1.21.8/build.gradle.kts @@ -1,10 +1,10 @@ extra.set("moduleName", "dev.faststats.neoforge.compat.v1_20_6") extra.set("publishVersionSuffix", "mc1.21-1.21.11") -java.toolchain.languageVersion = JavaLanguageVersion.of(25) +java.toolchain.languageVersion = JavaLanguageVersion.of(21) tasks.compileJava { - options.release.set(25) + options.release.set(21) } neoForge { diff --git a/neoforge/versions/1.21.9-1.21.11/build.gradle.kts b/neoforge/versions/1.21.9-1.21.11/build.gradle.kts index 90c00f3f..216d9edb 100644 --- a/neoforge/versions/1.21.9-1.21.11/build.gradle.kts +++ b/neoforge/versions/1.21.9-1.21.11/build.gradle.kts @@ -1,10 +1,10 @@ extra.set("moduleName", "dev.faststats.neoforge.compat.v1_21_9") extra.set("publishVersionSuffix", "mc1.21.9-1.21.11") -java.toolchain.languageVersion = JavaLanguageVersion.of(25) +java.toolchain.languageVersion = JavaLanguageVersion.of(21) tasks.compileJava { - options.release.set(25) + options.release.set(21) } neoForge { From 4e8a602ca0ec9754addd028c54f3634ace8817df Mon Sep 17 00:00:00 2001 From: david Date: Fri, 10 Jul 2026 18:58:20 +0200 Subject: [PATCH 06/11] Add adventure requirement to velocity module info --- velocity/src/main/java/module-info.java | 1 + 1 file changed, 1 insertion(+) diff --git a/velocity/src/main/java/module-info.java b/velocity/src/main/java/module-info.java index 0d80b4fc..daaa1636 100644 --- a/velocity/src/main/java/module-info.java +++ b/velocity/src/main/java/module-info.java @@ -9,6 +9,7 @@ requires com.velocitypowered.api; requires dev.faststats.config; requires dev.faststats; + requires net.kyori.adventure.api; requires org.slf4j; requires static org.jetbrains.annotations; From abc9c1052cea9e94be93751c7647e2b91f0eb174 Mon Sep 17 00:00:00 2001 From: david Date: Fri, 10 Jul 2026 18:59:46 +0200 Subject: [PATCH 07/11] Bump version to 0.27.3-pre1 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index e8d924ca..78803b45 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ -version=0.27.2 +version=0.27.3-pre1 org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m From 249a8f12972e6191dd9870a1eab8cb3800d6d28f Mon Sep 17 00:00:00 2001 From: david Date: Fri, 10 Jul 2026 19:08:42 +0200 Subject: [PATCH 08/11] [ci skip] Fix version suffix --- neoforge/versions/1.20.6-1.21.8/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neoforge/versions/1.20.6-1.21.8/build.gradle.kts b/neoforge/versions/1.20.6-1.21.8/build.gradle.kts index a60a502b..4001e1df 100644 --- a/neoforge/versions/1.20.6-1.21.8/build.gradle.kts +++ b/neoforge/versions/1.20.6-1.21.8/build.gradle.kts @@ -1,5 +1,5 @@ extra.set("moduleName", "dev.faststats.neoforge.compat.v1_20_6") -extra.set("publishVersionSuffix", "mc1.21-1.21.11") +extra.set("publishVersionSuffix", "mc1.20.6-1.21.8") java.toolchain.languageVersion = JavaLanguageVersion.of(21) From 293cb64f4bc2d44bacad5c874dc7c16913fafaaf Mon Sep 17 00:00:00 2001 From: david Date: Fri, 10 Jul 2026 19:23:59 +0200 Subject: [PATCH 09/11] Flatten NeoForge dependencies --- neoforge/build.gradle.kts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts index 7acc5add..11b02a16 100644 --- a/neoforge/build.gradle.kts +++ b/neoforge/build.gradle.kts @@ -31,12 +31,16 @@ subprojects { apply { plugin("net.neoforged.moddev") } - pluginManager.withPlugin("net.neoforged.moddev") { - dependencies { - "jarJar"(project(":neoforge")) - compileOnly("net.neoforged:bus:8.0.5") - compileOnlyApi(project(":neoforge")) - } + dependencies { + compileOnly("net.neoforged:bus:8.0.5") + compileOnlyApi(project(":neoforge")) + } + + tasks.jar { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + from(project(":neoforge").sourceSets["main"].output) + from(project(":config").sourceSets["main"].output) + from(project(":core").sourceSets["main"].output) } } From a6b426ef6ff018237baf6804d79eebd9d81d2d93 Mon Sep 17 00:00:00 2001 From: david Date: Fri, 10 Jul 2026 19:24:22 +0200 Subject: [PATCH 10/11] Bump version to 0.27.3-pre2 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 78803b45..3e635ed9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ -version=0.27.3-pre1 +version=0.27.3-pre2 org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m From 6a51dcb2566edd4910d192da7f367a8113d4047d Mon Sep 17 00:00:00 2001 From: david Date: Fri, 10 Jul 2026 20:04:38 +0200 Subject: [PATCH 11/11] Expand mods toml and json with version --- fabric/build.gradle.kts | 6 ++++++ neoforge/build.gradle.kts | 6 ++++++ neoforge/src/main/resources/META-INF/neoforge.mods.toml | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index a7f7f19d..699c8901 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -14,6 +14,12 @@ configurations.compileClasspath { attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 25) } +tasks.processResources { + filesMatching("fabric.mods.json") { + expand("version" to project.version) + } +} + allprojects { if (project.name == "example-mod") return@allprojects if (project.path == ":fabric:versions") return@allprojects diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts index 11b02a16..4b45893e 100644 --- a/neoforge/build.gradle.kts +++ b/neoforge/build.gradle.kts @@ -18,6 +18,12 @@ configurations.runtimeClasspath { attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 25) } +tasks.processResources { + filesMatching("META-INF/neoforge.mods.toml") { + expand("version" to project.version) + } +} + allprojects { if (project.name == "example-mod") return@allprojects if (project.path == ":neoforge:versions") return@allprojects diff --git a/neoforge/src/main/resources/META-INF/neoforge.mods.toml b/neoforge/src/main/resources/META-INF/neoforge.mods.toml index bfef8dde..a2682566 100644 --- a/neoforge/src/main/resources/META-INF/neoforge.mods.toml +++ b/neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -5,7 +5,7 @@ issueTrackerURL = "https://github.com/faststats-dev/faststats-java/issues" [[mods]] modId = "faststats" -version = "${file.jarVersion}" +version = "${version}" displayName = "FastStats" description = "Analytics for developers" logoFile = "faststats.png"