Skip to content
Basti edited this page Sep 27, 2026 · 5 revisions

TurtleLib is a shared helper library for Paper plugins: update checks, server-version guards, localization, and careful config file maintenance. It isn't a server plugin of its own; each plugin shades it into its jar.

Getting Started 🚀

1️⃣ Build the Library

git clone https://github.com/hrobasti/turtle-lib.git
cd turtle-lib
./gradlew clean build   # Windows: .\gradlew.bat clean build

The build needs Java 25 and reads its Paper, JUnit and Gradle plugin versions from the repo's own version catalog gradle/libs.versions.toml, so a fresh standalone clone builds without any surrounding workspace.

The jar lands in dist/turtle-lib-paper-<version>.jar, the sources jar in dist/sources/, each with a .sha256 checksum next to it. dist/ is wiped at the start of every build. build also runs the Spotless format check; ./gradlew spotlessApply fixes formatting.

2️⃣ Add It to Your Plugin

TurtleLib isn't published to a Maven repository. Tagged releases attach the jar to GitHub Releases, and Gradle reads it straight from there through an ivy repository. Wrap that repository in exclusiveContent, as Timberella and UnderwaterTrees do: the group turtle-lib is then looked up only there, and no other dependency is ever requested from GitHub.

repositories {
    mavenCentral()
    maven {
        name = 'papermc-repo'
        url = uri('https://repo.papermc.io/repository/maven-public/')
    }
    // TurtleLib comes only from its GitHub Release assets, and nothing else is read from there.
    exclusiveContent {
        forRepository {
            ivy {
                url = uri('https://github.com/hrobasti/turtle-lib/releases/download')
                patternLayout {
                    artifact '[revision]/[artifact]-[revision].[ext]'
                }
                metadataSources { artifact() }
            }
        }
        filter { includeGroup 'turtle-lib' }
    }
}

dependencies {
    compileOnly('io.papermc.paper:paper-api:26.3.build.49-alpha')
    implementation('turtle-lib:turtle-lib-paper:2.0.0') // must match a release tag and its asset name
}
  • Shade TurtleLib into your plugin jar, e.g. with the Shadow plugin, as Timberella and UnderwaterTrees do.
  • TurtleLib declares Paper API as compileOnly, so declare it compileOnly in your plugin too. Gson and Adventure/MiniMessage come with it; don't declare or shade your own copies.
  • LocaleFileChecker is only meant for unit tests. Keep it out of the shaded jar with exclude 'net/kroet/turtlelib/helper/LocaleFileChecker*' in the shadowJar block.
  • TurtleLib is under the MIT License, which asks you to keep its copyright and license notice with every copy, so ship it in your jar. TurtleLib's jar has it as META-INF/LICENSE, where shading tends to drop it or overwrite it with your own license; Timberella and UnderwaterTrees therefore exclude that file in shadowJar and ship the text as licenses/turtle-lib-MIT.txt instead.
  • Prefer a local copy? Put the jar into libs/ and use implementation(files('libs/turtle-lib-paper-<version>.jar')).

3️⃣ Work on TurtleLib Alongside a Plugin

  • Timberella and UnderwaterTrees: clone TurtleLib as a sibling (../turtle-lib) and build the plugin with -PuseLocalTurtleLib=true. The plugin then builds against that checkout instead of the release jar.
  • Other projects: ./gradlew publishToMavenLocal publishes net.kroet.turtlelib:turtle-lib:<version>. Add mavenLocal { content { includeGroup 'net.kroet.turtlelib' } } to the consumer's repositories and depend on net.kroet.turtlelib:turtle-lib:<version>. Keep the filter: an unfiltered mavenLocal() in front of mavenCentral() lets whatever is in your local Maven cache win for every dependency.

Helpers 🧰

Every helper page lists what the helper can't do, and its known issues, under Limitations ⚠️. Read that section before you rely on a helper.

  • UpdateChecker – polls Modrinth and/or Hangar, filters prereleases and server versions, and reports whether a newer release exists.
  • VersionComparator – documents the version format and comparison rules, and how to use them in guardrails or update prompts.
  • ServerMatcher – declares supported Minecraft versions and warns or disables the plugin on unsupported servers.
  • ConfigWatcher – watches a file for changes off the main thread and runs your reload callback on the main thread.
  • StartupBanner – prints a MiniMessage-colored banner from a bundled resource or inline lines during plugin start.
  • MessageService – loads the active language file, renders MiniMessage messages with prefix and placeholders, and logs localized console lines.
  • LangLoader – discovers bundled locales, copies them into the data folder, and merges new keys into existing files.
  • ConfigKeyMigrator – renames legacy config/lang keys in place across releases without wiping admins' comments or formatting.
  • ConfigDefaultsInserter – adds new default keys to an existing config file as text, with their comments, while every other byte stays as the admin left it.
  • LegacyDataUpgrade – runs the one-time clean upgrade after a major release: backup folder, fresh files, and carrying over only the values the admin had changed.
  • FileChangeTracker – lists which settings and files changed since the last reload, so admins see what a reload picked up.
  • LocaleFileChecker – checks a plugin's lang files in a unit test: same keys and tags as the base locale, valid MiniMessage, no untranslated values, and keys used in code against keys defined.

Choosing a Config Upgrade Path 🗺️

  • Renamed keys: ConfigKeyMigrator, in place, comments stay. rewriteLegacyKeysInFile renames by key name in every section, for style renames like kebab-case → snake_case; rewriteKeyPathsInFile renames single paths and moves keys between sections.
  • New settings: ConfigDefaultsInserter, added as text with their comments.
  • A major release that changes format, defaults and keys at once: LegacyDataUpgrade, once, with a backup and fresh files.

Happy coding! 🚀

Clone this wiki locally