- GhostData: worldName field, format v4; GhostManager stores last N runs per player (ghosts/<uuid>/<ts>.ghost)
- GhostService: per-player recording (start on run start, every tick, finalize on fall), gated by ghost-mode.enabled + per-player toggle
- PlayerGhost: packet-based fake-player NPC with viewer skin (NMS via remapNms gradle task, reflection send, graceful degradation)
- GhostReplay: teleport viewer to run start, replay frames via PlayerGhost, restore on finish
- /lp replay [index] command + tab completion; settings menu ghost_recording toggle (persisted disk/SQL)
- config: ghost-mode.{enabled,max-runs}, remove dead show-top/transparency + GHOST_* statics
- tests: GhostData v4 round-trip, GhostManager eviction/ordering, yawToFixed (79 total)
314 lines
11 KiB
Kotlin
314 lines
11 KiB
Kotlin
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")
|
|
id("io.github.goooler.shadow") version("8.1.8")
|
|
id("xyz.jpenilla.run-paper") version("3.0.2")
|
|
checkstyle
|
|
pmd
|
|
}
|
|
|
|
group = "dev.loki"
|
|
version = "1.3.3"
|
|
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 = "mv-repo"
|
|
url = uri("https://repo.onarandombox.com/content/groups/public/")
|
|
}
|
|
|
|
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) }
|
|
|
|
// 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 are referenced by the NMS packet signatures (IntList).
|
|
compileOnly("it.unimi.dsi:fastutil:8.5.9")
|
|
|
|
// Adventure API (for Component)
|
|
compileOnly("net.kyori:adventure-api:4.14.0")
|
|
compileOnly("net.kyori:adventure-text-minimessage:4.14.0")
|
|
|
|
// LoLib Core
|
|
implementation(files("libs/lolib-3.0.0.jar"))
|
|
|
|
// Shaded dependencies
|
|
implementation("io.papermc:paperlib:1.0.7")
|
|
implementation("com.google.code.gson:gson:2.10.1")
|
|
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")
|
|
compileOnly("com.sk89q.worldedit:worldedit-core:7.3.6")
|
|
compileOnly("com.sk89q.worldedit:worldedit-bukkit:7.3.6")
|
|
|
|
// 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")
|
|
|
|
// Annotations
|
|
compileOnly("org.jetbrains:annotations:24.1.0")
|
|
|
|
// Testing
|
|
testImplementation("org.junit.jupiter:junit-jupiter:5.11.0-M1")
|
|
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("dev.lolib", "dev.loki.loparkour.lib.lolib")
|
|
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")
|
|
|
|
// Don't minimize - causes issues with Caffeine's dynamically generated classes
|
|
// minimize {
|
|
// exclude(dependency("com.zaxxer:HikariCP:.*"))
|
|
// exclude(dependency("com.mysql:mysql-connector-j:.*"))
|
|
// exclude(dependency(files("libs/lolib-3.0.0.jar")))
|
|
// }
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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/**")
|
|
}
|
|
|
|
// Default task
|
|
defaultTasks("clean", "build")
|