-
-
Notifications
You must be signed in to change notification settings - Fork 0
2 ‐ Developer Guide
This document targets contributors who need to build UnderwaterTrees from source, extend functionality, or troubleshoot code-level issues. For installing and configuring the plugin, see Home.
plugins/underwatertrees-plugin/
├─ build.gradle # Paper plugin build script (shadowJar enabled)
├─ settings.gradle # Opt-in ../turtle-lib checkout (-PuseLocalTurtleLib=true)
├─ gradle/libs.versions.toml # Version catalog, a synced copy of the workspace root's catalog
├─ version.properties # Plugin version
├─ config/ # Spotless (Eclipse JDT) formatter settings
├─ src/main/java/... # Plugin source
├─ src/main/resources/ # Config, locale templates, bundled licenses/, legacy/ upgrade baselines (1.x, 1.0.x)
├─ src/test/java/... # Unit tests
├─ dist/ # Build output (wiped on every build)
├─ LICENSE # MIT license text, copied into the jar
└─ THIRD_PARTY_LICENSES.md
-
Java 25 (Temurin or equivalent). Configure
JAVA_HOMEaccordingly. -
Gradle Wrapper (Gradle 9.8.0; do not install system Gradle; use
./gradlew/gradlew.bat). The configuration cache is enabled ingradle.properties, so repeated builds reuse the configuration phase. - Git for version control, and an IDE of choice (IntelliJ IDEA, VS Code with Java extensions).
Dependency and Paper versions come from the repo's own version catalog gradle/libs.versions.toml, which Gradle loads automatically; it is a copy of the workspace root's catalog, kept in sync by sync-gradle-wrapper.ps1. Always make version bumps (including those from Dependabot PRs) in the root catalog first, then run sync-gradle-wrapper.ps1; the script aborts without copying anything if a repo's copy was edited directly since the last sync, and lists the differing entries (-Force overwrites them). Adventure and Gson are not in the catalog: they come in through paper-api, which fixes their versions.
./gradlew clean build # compiles + checks formatting + runs tests + produces jars and checksums
./gradlew shadowJar # only build the shaded Paper artifact (with its .sha256)
./gradlew test # run unit tests (no running server needed)
./gradlew spotlessApply # fix formatting (spotlessCheck runs as part of build)Artifacts land in dist/underwatertrees-paper-<version>.jar (sources jar in dist/sources/), each with a .sha256 file next to it (verifiable with sha256sum -c). Every jar task writes the checksums itself, so a partial build like shadowJar never leaves a jar without one. The cleanDist task wipes dist/ before every jar build (a lone shadowJar removes the sources jar too), so run the full build for a release and move releases you want to keep elsewhere first.
The build compiles against Paper 26.3.build.49-alpha (pinned in the version catalog).
- Unit tests: Keep them deterministic and focused on pure logic (e.g., packed sapling coordinates, the tracked-sapling queue, spawn protection, 1.x key mappings, locale files).
-
Manual QA: Deploy the shaded jar to a Paper test server, enable
log_stats/log_detail, and exercise config reloads, update notifications, and sapling placement. - Performance: Watch timings when enabling light override or mass sapling placement; adjust intervals/batch sizes before release if necessary.
- Target Java 25 features conservatively (avoid exotic APIs that inflate class files).
- Player-facing text must live in
lang/*.ymland be rendered viaMessageService. - Prefer dependency injection via constructors; avoid static singletons where possible.
- Keep placement/protection logic thread-safe. Event handlers, commands and config watcher callbacks all run on the main thread (
ConfigWatcherpolls in the background but hands the callback over via the scheduler). Only the first-time chunk scans run on a background pool; they read aChunkSnapshotand hand their results back to the main thread, so keep Bukkit API calls out of that code. -
/underwatertrees reloadand the config watcher both runreloadAndMergeConfig(), which re-applies the config to the existing listener and refreshes locales, metrics, update checker, and watcher baseline on the main thread. File writes happen synchronously there, and only when something changed:ConfigKeyMigratorrenames old keys,ConfigDefaultsInserteradds missing keys toconfig.ymlas text (entries admins deleted fromsoils/saplingsstay deleted, tracked in.seen-defaults.yml), andLangLoaderadds missing keys to the active lang file. IfConfigWatcher.describeYamlError()finds thatconfig.ymlisn't valid YAML, it changes nothing and returns the error for the command's reply; at startup, the plugin then runs on the bundledconfig.ymlwithout bStats and update checks until a reload succeeds. The watcher never reloads a deleted or emptiedconfig.yml; itsonSkippedhook logslog.config_missing_externalorlog.config_empty_externalinstead, and only the command or a restart writes the file again. Keep expensive I/O off the main thread and profile changes before shipping. - Check Paper internals on every Paper build update. Underwater planting fires its own
BlockPlaceEventso protection plugins can veto it (UnderwaterSaplingsListener#onPlaceUnderwater), and Paper marks that constructor@ApiStatus.Internal. After bumping the Paper build, compile against it and plant a sapling underwater on a test server. If a Paper update breaks the constructor at runtime,InternalEventGuarddenies the planting and logs one warning per server run instead of placing the sapling unprotected. - Underwater dark and pale oaks: vanilla's
DarkOakTrunkPlaceronly grows the trunk into air or leaves, soUnderwaterTwoByTwoGrowthcancels the trunklessStructureGrowEvent, drains the cells the trunk can take (their bounds mirrorDarkOakTrunkPlacer(6, 2, 1): up to 9 layers, a lean of up to 2 blocks), grows the tree again withWorld#generateTree, only collecting its blocks, and fires them as its ownStructureGrowEvent(location, player and bone meal flag of the dropped one;InternalEventGuardcovers its internal constructor). Only the blocks that event leaves are set, and the water is refilled in the same tick; a cancelled or emptied event restores everything and gives the bone meal back. UnderwaterTrees skips its own event by identity. Recheck those bounds, and that the pale oak sapling still grows the world-gen pale oak minus its pale moss, when Minecraft changes these trees.
- UnderwaterTrees pulls
MessageService,LangLoader,ConfigWatcher,UpdateChecker,ServerMatcher,StartupBanner,ConfigKeyMigrator,ConfigDefaultsInserter,FileChangeTracker, andLegacyDataUpgradefrom TurtleLib. TurtleLib (MIT, by kroet.net) is shaded into the plugin jar; its license text ships assrc/main/resources/licenses/turtle-lib-MIT.txt. The tests also useLocaleFileChecker, whichshadowJarleaves out of the plugin jar. - By default, TurtleLib is resolved as
turtle-lib:turtle-lib-paper:<version>from its tagged GitHub Release assets, so no checkout is needed (seegradle/libs.versions.tomlfor the pinned version andbuild.gradle'sivyrepository for how it's fetched;exclusiveContentmakes that the only source for TurtleLib, and the Gradle wrapper checks its download against the official SHA-256). If Gradle can't find it, check that the pinned version has a matching tag and release asset on TurtleLib. - Editing TurtleLib alongside this plugin: clone it as a sibling (
git clone https://github.com/hrobasti/turtle-lib ../turtle-lib) and build with-PuseLocalTurtleLib=true— this switchessettings.gradle/build.gradleto a local project dependency that picks up source changes automatically, no publish step needed.../turtle-libmust then be checked out relative to this repo.
- Add keys to
src/main/resources/lang/en_US.ymlfirst. - Mirror keys across other locales (machine translations acceptable, but mark TODOs if manual review is pending).
- Run the plugin once;
MessageService#loadadds new keys to the active locale's file inplugins/UnderwaterTrees/lang/without overwriting user customizations (other locales get theirs once they are loaded). To debug the sync, delete a locale fromplugins/UnderwaterTrees/lang/to force a fresh copy, or add temporary logging aroundMessageService#load, which syncs throughLangLoader.loadActiveLocale. - Keep placeholders consistent (
<prefix>,<prefix_label>,<code>, etc.). Document any new placeholders on the Home page and add them toLocaleConsistencyTest. That test (TurtleLib'sLocaleFileChecker) fails the build on missing or extra keys, changed tags, invalid MiniMessage, values left identical toen_US(outside a short allowlist), and keys the code uses buten_US.ymllacks or vice versa. - Renaming a config or lang key? Add an old → new entry to
LEGACY_CONFIG_KEY_MIGRATIONS/LEGACY_LANG_KEY_MIGRATIONSinUnderwaterTreesPluginso existing files are rewritten in place;LegacyConfigMappingTestandLegacyLangMigrationTestcheck both tables against the 1.x files inlegacy/1.x/.
-
version.propertiesdefines the release version. If missing, Gradle falls back togradle.propertiesor-Pversion. Update it when cutting a release sopaper-plugin.ymland artifact names stay consistent. - Tag releases with exactly the version from
version.properties, without avprefix (e.g.2.0.1; pre-releases as2.1.0-beta2, by convention without a dot before the number). Use the same string as the version number on Modrinth and Hangar. The update checker also reads common variants (v2.1.0,2.1.0-beta.2), but stick to the format under "Version Format" on the TurtleLib wiki's VersionComparator page, as some spellings still sort unexpectedly (e.g. dates). Existing tags stay as they are.
Release checklist:
- Update
version.propertiesand any changelog notes. - Run
./gradlew clean buildand ensure tests pass; archive any older jars fromdist/first (e.g. intodist-archive/), since the build wipes it. - Verify that
THIRD_PARTY_LICENSES.mdandsrc/main/resources/licenses/reflect dependency changes; a new bundled library needs a license compatible with MIT distribution. - Publish the jar from
dist/together with its.sha256file (Modrinth, Hangar as applicable). - Announce new locales or config keys in the README/wiki if user-facing behavior changed.
Contributing:
- Fork the repository, create feature branches (
feature/<topic>), and open pull requests againstdefault. - Keep commits focused; reference issue numbers in commit messages when relevant.
- Expect code review focusing on API stability, permissions security, and localization.
- Contributions are accepted under the MIT License.
- New third-party libraries: update
build.gradleandTHIRD_PARTY_LICENSES.md, ship the library's license text undersrc/main/resources/licenses/if it is bundled, and confirm the license is compatible with distribution under the MIT License. - Parts of this plugin and its documentation were produced with AI assistance and reviewed by the maintainer before release. Review every patch, generated or not, for Paper compliance and licensing.