LoParkour/build.gradle.kts
loki5512344 02e2947f47
refactor(player): encapsulate game state behind PlayerSettings, clear VisibilityModifier and HiddenField
- ParkourUser/ParkourPlayer/GeneratorState public mutable fields -> private + short accessors
- per-player settings extracted to player/data/PlayerSettings (ParkourPlayer 188 -> 96 lines,
  PMD-counted methods 20 -> 9); this is what fixed the TooManyMethods violation that the
  encapsulation itself introduced — by decomposition, not by lowering the threshold
- disk storage reads and writes the same canonical 12-key map as the SQL JSON column;
  legacy key names normalized on read, @Expose dropped from domain classes
- 39 settings call sites rewritten across 7 files
- HiddenField backlog cleared (Config, ObstacleGenerator, GhostService, Leaderboard,
  PagedMenu, Reward, ParticleData, World)
- checkstyle ratchet: VisibilityModifier, HiddenField, EmptyLineSeparator and
  MissingDeprecated flipped to error once their counters hit zero; suppressions.xml empty
- javac -Xmaxwarns raised so NullAway/Error Prone counts are honest
  (baseline 165/289, now 164/265)

checkstyle 0 errors / 135 warnings, PMD 0 violations, tests 94/0 fail, ./gradlew check green.
TODO.md: items 30 and 107 updated, new items 167-170, ПРОГРЕСС-6/7 recorded.
2026-10-06 18:57:39 +02:00

419 lines
15 KiB
Kotlin

import net.ltgt.gradle.errorprone.errorprone
import java.io.File
import java.net.URI
import java.util.zip.ZipFile
// Remap tooling: rebuilds the Mojang-mapped 1.20.4 NMS classes at build time.
buildscript {
repositories {
mavenCentral()
maven("https://maven.fabricmc.net/")
}
dependencies {
classpath("net.fabricmc:mapping-io:0.6.1")
classpath("net.fabricmc:tiny-remapper:0.9.0")
}
}
plugins {
id("java-library")
id("com.gradleup.shadow") version("9.6.1")
id("xyz.jpenilla.run-paper") version("3.0.2")
checkstyle
pmd
jacoco
id("net.ltgt.errorprone") version("4.3.0")
id("com.diffplug.spotless") version("7.0.4")
id("com.github.spotbugs") version("6.1.13")
id("de.thetaphi.forbiddenapis") version("3.9")
id("com.autonomousapps.dependency-analysis") version("2.19.0")
id("org.owasp.dependencycheck") version("12.1.3")
}
group = "dev.loki"
version = "1.4.0"
description = "LoParkour - Advanced parkour plugin for Minecraft"
// Multi-version build: pass -PmcVersion / -PapiCoordinate / -PjavaRelease / -PapiVersion
// to build the same source against a different Paper API (e.g. GitHub Actions matrix).
val mcVersion: String = providers.gradleProperty("mcVersion").getOrElse("1.20.4")
val apiCoordinate: String = providers.gradleProperty("apiCoordinate")
.getOrElse("org.spigotmc:spigot-api:$mcVersion-R0.1-SNAPSHOT")
val javaRelease: Int = providers.gradleProperty("javaRelease").map { it.toInt() }.getOrElse(21)
val apiVersion: String = providers.gradleProperty("apiVersion").getOrElse("1.20")
repositories {
mavenCentral()
maven {
name = "papermc"
url = uri("https://repo.papermc.io/repository/maven-public/")
}
maven {
name = "spigot-repo"
url = uri("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
}
maven {
name = "jitpack"
url = uri("https://jitpack.io")
}
maven {
name = "maven-central"
url = uri("https://oss.sonatype.org/content/groups/public")
}
maven {
name = "placeholderapi"
url = uri("https://repo.extendedclip.com/content/repositories/placeholderapi/")
}
maven {
name = "dmulloy2-repo"
url = uri("https://repo.dmulloy2.net/repository/public/")
}
maven {
name = "codemc-repo"
url = uri("https://repo.codemc.io/repository/maven-public/")
}
maven {
name = "opencollab-snapshot-repo"
url = uri("https://repo.opencollab.dev/maven-snapshots/")
}
// For ConfigUpdater dependency
maven {
name = "codemc-snapshots"
url = uri("https://repo.codemc.io/repository/maven-snapshots/")
}
maven {
name = "enginehub"
url = uri("https://maven.enginehub.org/repo/")
}
}
// --- Mojang-mapped NMS (net.minecraft.*) for the packet NPC renderer ---------
// repo.papermc.io purged the old io.papermc.paper:paper:1.20.4-R0.1-SNAPSHOT
// dev-bundle, so the 1.20.4 vanilla server jar (bundler/obfuscated) is remapped
// with Mojang's official mappings via tiny-remapper into the same Mojang-mapped
// classes the dev-bundle shipped. Result is cached under the Gradle user home.
val nmsVersion = "1.20.4"
val nmsCacheDir = gradle.gradleUserHomeDir.resolve("caches/loparkour-nms/$nmsVersion")
val nmsServerJar = nmsCacheDir.resolve("server.jar")
val nmsNestedJar = nmsCacheDir.resolve("server-nested.jar")
val nmsMappings = nmsCacheDir.resolve("server-mappings.txt")
val nmsTiny = nmsCacheDir.resolve("server-mappings.tiny")
val nmsMappedJar = nmsCacheDir.resolve("server-mojang-mapped.jar")
val nmsAuthlibJar = nmsCacheDir.resolve("authlib-6.0.52.jar")
val remapNms by tasks.registering {
outputs.files(nmsServerJar, nmsMappings, nmsMappedJar, nmsAuthlibJar)
doLast {
nmsCacheDir.mkdirs()
fun download(url: String, target: File) {
if (target.isFile) {
return
}
URI(url).toURL().openStream().use { input ->
target.outputStream().use { input.copyTo(it) }
}
}
download("https://piston-data.mojang.com/v1/objects/8dd1a28015f51b1803213892b50b7b4fc76e594d/server.jar", nmsServerJar)
download("https://piston-data.mojang.com/v1/objects/c1cafe916dd8b58ed1fe0564fc8f786885224e62/server.txt", nmsMappings)
download("https://libraries.minecraft.net/com/mojang/authlib/6.0.52/authlib-6.0.52.jar", nmsAuthlibJar)
if (!nmsNestedJar.isFile) {
ZipFile(nmsServerJar).use { zip ->
zip.getInputStream(zip.getEntry("META-INF/versions/$nmsVersion/server-$nmsVersion.jar")).use { input ->
nmsNestedJar.outputStream().use { input.copyTo(it) }
}
}
}
val tree = net.fabricmc.mappingio.tree.MemoryMappingTree(false)
net.fabricmc.mappingio.MappingReader.read(nmsMappings.toPath(), net.fabricmc.mappingio.format.MappingFormat.PROGUARD_FILE, tree)
net.fabricmc.mappingio.MappingWriter.create(nmsTiny.toPath(), net.fabricmc.mappingio.format.MappingFormat.TINY_2_FILE).use { writer ->
tree.accept(net.fabricmc.mappingio.adapter.MappingSourceNsSwitch(writer, "target"))
}
val remapper = net.fabricmc.tinyremapper.TinyRemapper.newRemapper()
.withMappings(net.fabricmc.tinyremapper.TinyUtils.createTinyMappingProvider(nmsTiny.toPath(), "target", "source"))
.ignoreConflicts(true)
.rebuildSourceFilenames(true)
.build()
net.fabricmc.tinyremapper.OutputConsumerPath.Builder(nmsMappedJar.toPath()).build().use { output ->
output.addNonClassFiles(nmsNestedJar.toPath())
remapper.readInputs(nmsNestedJar.toPath())
remapper.apply(output)
}
remapper.finish()
}
}
tasks.withType<JavaCompile>().configureEach { dependsOn(remapNms) }
// Analysis tasks (dependency-analysis, spotbugs, ...) read the remapped NMS jars too.
tasks.matching { it.name != "remapNms" && it.name != "clean" }.configureEach { mustRunAfter(remapNms) }
// Resolve the org.bukkit:bukkit capability conflict that newer paper-api artifacts
// introduce (they both bundle the bukkit classes and declare the capability).
configurations.configureEach {
resolutionStrategy.capabilitiesResolution.withCapability("org.bukkit:bukkit") {
selectHighestVersion()
}
}
dependencies {
// Spigot/Paper API (overridable per MC version via -PapiCoordinate).
// Newer paper-api artifacts declare a conflicting org.bukkit:bukkit capability
// (they bundle the classes themselves) — drop the duplicate transitive.
if (apiCoordinate.startsWith("io.papermc.paper:paper-api")) {
val (apiGroup, apiName, apiVersion) = apiCoordinate.split(":")
compileOnly(mapOf(
"group" to apiGroup,
"name" to apiName,
"version" to apiVersion,
"exclude" to mapOf("group" to "org.bukkit", "module" to "bukkit")
))
} else {
compileOnly(apiCoordinate)
}
// NMS (net.minecraft.*) for packet NPC rendering — compileOnly, Mojang-mapped.
// Built once by the remapNms task above (vanilla 1.20.4 server + official
// mappings) because the io.papermc.paper:paper:1.20.4 dev-bundle was purged.
compileOnly(files(nmsMappedJar, nmsAuthlibJar))
// fastutil types (IntList) are referenced by the NMS packet signatures. 8.5.15
// matches the version pinned by the new-style paper-api platform (1.21.11+/26.x)
// and resolves fine against the older paper-api artifacts too.
compileOnly("it.unimi.dsi:fastutil:8.5.15")
// Adventure API (for Component)
compileOnly("net.kyori:adventure-api:4.14.0")
compileOnly("net.kyori:adventure-text-minimessage:4.14.0")
// Shaded dependencies
implementation("io.papermc:paperlib:1.0.7")
implementation("com.google.code.gson:gson:2.11.0")
implementation("org.bstats:bstats-bukkit:3.2.1")
implementation("com.zaxxer:HikariCP:5.1.0")
implementation("com.mysql:mysql-connector-j:8.3.0")
implementation("org.apache.commons:commons-math3:3.6.1")
// worldedit-core brings its own platform constraint on fastutil (strictly 8.5.12),
// which conflicts with the new-style paper-api platform (8.5.15+); we only need
// worldedit's own classes (Clipboard, BlockVector3, ...), not its transitive deps.
compileOnly("com.sk89q.worldedit:worldedit-core:7.3.6") { isTransitive = false }
compileOnly("com.sk89q.worldedit:worldedit-bukkit:7.3.6") { isTransitive = false }
// Provided dependencies (plugins)
compileOnly("me.clip:placeholderapi:2.11.6")
compileOnly("com.github.MilkBowl:VaultAPI:1.7")
compileOnly("me.filoghost.holographicdisplays:holographicdisplays-api:3.0.0-SNAPSHOT")
compileOnly("org.geysermc.floodgate:api:2.0-SNAPSHOT")
// Error Prone + NullAway (compile-time bug/null-safety checks)
errorprone("com.google.errorprone:error_prone_core:2.41.0")
errorprone("com.uber.nullaway:nullaway:0.12.10")
// Annotations
compileOnly("org.jetbrains:annotations:24.1.0")
// Testing
testImplementation("org.junit.jupiter:junit-jupiter:5.11.0-M1")
testImplementation("com.tngtech.archunit:archunit-junit5:1.4.1")
testImplementation("org.mockito:mockito-core:5.10.0")
testImplementation("org.mockito:mockito-junit-jupiter:5.10.0")
testImplementation("org.spigotmc:spigot-api:1.20.4-R0.1-SNAPSHOT")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
// Test runtime classpath for loading PlayerGhost (references NMS + authlib types).
testImplementation(files(nmsMappedJar, nmsAuthlibJar))
}
tasks.shadowJar {
archiveClassifier.set("")
archiveFileName.set("LoParkour-${project.version}-mc${mcVersion}.jar")
// Relocate shaded dependencies
relocate("io.papermc.lib", "dev.loki.loparkour.lib.paperlib")
relocate("com.google.gson", "dev.loki.loparkour.lib.gson")
relocate("org.bstats", "dev.loki.loparkour.lib.bstats")
relocate("org.apache.commons.math3", "dev.loki.loparkour.lib.commonsmath3")
}
tasks.named("build") {
dependsOn(tasks.named("shadowJar"))
}
tasks.withType<ProcessResources> {
filesMatching(listOf("plugin.yml", "config.yml")) {
expand(
"version" to project.version,
"description" to (project.description ?: ""),
"apiVersion" to apiVersion
)
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
tasks.withType<JavaCompile> {
options.encoding = "UTF-8"
options.release = javaRelease
options.compilerArgs.addAll(listOf("-Xmaxwarns", "100000"))
options.errorprone {
disableWarningsInGeneratedCode = true
// NullAway: only code annotated @NullMarked-style packages is checked once adopted;
// start as warnings so the baseline is visible but does not block.
check("NullAway", net.ltgt.gradle.errorprone.CheckSeverity.WARN)
option("NullAway:AnnotatedPackages", "dev.loki.loparkour")
option("NullAway:ExcludedFieldAnnotations", "org.jetbrains.annotations.Nullable")
}
if (name == "compileTestJava") {
options.errorprone.isEnabled.set(false)
}
}
tasks.withType<Javadoc> {
options.encoding = "UTF-8"
}
// Run paper server via gradlew runServer
tasks {
runServer {
minecraftVersion("1.21.4")
jvmArgs("-Xms2G", "-Xmx2G")
runDirectory.set(layout.projectDirectory.dir("run"))
downloadPlugins {
modrinth("placeholderapi", "2.11.6")
}
}
}
checkstyle {
toolVersion = "10.21.4"
configFile = file("config/checkstyle/checkstyle.xml")
isIgnoreFailures = false
maxErrors = 0
// Warnings are NOT blocking: noisy/opinionated rules (MagicNumber,
// FinalLocalVariable, EmptyLineSeparator, ClassFanOutComplexity, ...) run at
// severity="warning" in checkstyle.xml and surface in build/reports/checkstyle/.
}
pmd {
toolVersion = "7.0.0"
ruleSetFiles = files(rootProject.file("config/pmd/ruleset.xml"))
// PMD is now a HARD GATE (0 violations). Any new violation fails the build.
isIgnoreFailures = false
maxFailures = 0
}
tasks.withType<Checkstyle>().configureEach {
reports {
xml.required.set(true)
html.required.set(true)
}
}
tasks.withType<Pmd>().configureEach {
reports {
xml.required.set(true)
html.required.set(true)
}
// Ignore generated/shaded sources and tests for now (test sources are not analyzed).
exclude("**/build/generated/**")
}
// ---------------------------------------------------------------------------
// Extra quality gates (see TODO.md, task 2). Existing debt is recorded, new
// violations fail the build; tighten thresholds as the refactor progresses.
// ---------------------------------------------------------------------------
jacoco {
toolVersion = "0.8.13"
}
tasks.test {
finalizedBy(tasks.jacocoTestReport)
}
tasks.jacocoTestReport {
dependsOn(tasks.test)
reports {
xml.required.set(true)
html.required.set(true)
}
}
tasks.jacocoTestCoverageVerification {
violationRules {
rule {
limit {
counter = "LINE"
minimum = "0.05".toBigDecimal() // baseline; raise toward 0.5 (core 0.7)
}
}
}
}
tasks.check {
dependsOn(tasks.jacocoTestCoverageVerification, "cpdCheck")
}
spotless {
java {
target("src/**/*.java")
removeUnusedImports()
trimTrailingWhitespace()
endWithNewline()
}
}
spotbugs {
toolVersion = "4.9.3"
effort = com.github.spotbugs.snom.Effort.MAX
reportLevel = com.github.spotbugs.snom.Confidence.MEDIUM
ignoreFailures = true // report-only until the baseline is triaged
}
tasks.withType<com.github.spotbugs.snom.SpotBugsTask>().configureEach {
reports.create("html") { required = true }
}
forbiddenApis {
bundledSignatures = setOf("jdk-unsafe-21", "jdk-deprecated-21", "jdk-system-out", "jdk-non-portable")
ignoreFailures = true // report-only until the baseline is triaged
}
// Copy/paste detector (DRY). The de.aaschmid.cpd plugin does not support PMD 7, so run PMD's CPD directly.
val cpdTool: Configuration by configurations.creating
dependencies {
cpdTool("net.sourceforge.pmd:pmd-cli:7.0.0")
cpdTool("net.sourceforge.pmd:pmd-java:7.0.0")
}
val cpdCheck by tasks.registering(JavaExec::class) {
group = "verification"
description = "Finds duplicated code (min 100 tokens). Report-only until duplicates are triaged."
classpath = cpdTool
mainClass.set("net.sourceforge.pmd.cli.PmdCli")
args("cpd", "--minimum-tokens", "100", "--language", "java", "--dir", "src/main/java",
"--format", "text")
isIgnoreExitValue = true // exit code 4 = duplicates found
doFirst {
val dir = layout.buildDirectory.dir("reports/cpd").get().asFile
dir.mkdirs()
standardOutput = File(dir, "cpd.txt").outputStream()
}
}
dependencyCheck {
// Run explicitly: ./gradlew dependencyCheckAnalyze (set NVD_API_KEY for speed).
failBuildOnCVSS = 7.0f
nvd.apiKey = System.getenv("NVD_API_KEY")
}
// Default task
defaultTasks("clean", "build")