-
-
Notifications
You must be signed in to change notification settings - Fork 0
2 ‐ Developer Guide
This document targets contributors who need to build Timberella from source, extend functionality, or troubleshoot code-level issues. Configuration, commands and permissions are documented for server admins on the wiki Home page.
plugins/timberella-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 (package net.kroet.timberella)
├─ src/main/resources/ # Config, locales, leaf mappings, bundled license texts, legacy/ upgrade baselines
├─ src/test/java/... # Unit tests
├─ LICENSE # MIT license text, copied into the jar
└─ THIRD_PARTY_LICENSES.md
Library and Paper versions come from 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. How TurtleLib is resolved is explained in §5.
-
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 on (gradle.properties), so a repeated build skips the configuration phase; build-script code that runs at execution time must not reach back intoprojectorlayout. - Git for version control.
- IDE of choice (IntelliJ IDEA, VS Code with Java extensions).
- Compiles against Paper API
26.3.build.49-alpha(pinned in the version catalog);paper-plugin.ymldeclaresapi-version: "26.3". On every Paper build update, check the Paper internals listed under Coding Guidelines (§6).
./gradlew clean build # compiles + formatting check + tests + shaded jar, sources jar and checksums
./gradlew shadowJar # only build the shaded Paper artifact (with its .sha256)
./gradlew test # run unit tests (no running server needed)
./gradlew spotlessApply # auto-format Java and resource files (build fails on unformatted code)Artifacts land in dist/timberella-paper-<version>.jar and dist/sources/timberella-paper-<version>-sources.jar, each with a .sha256 file next to it (verify 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 copy anything you want to keep elsewhere first.
-
Unit tests live under
src/test/java. Prefer deterministic tests (no Bukkit mocks unless necessary).LocaleConsistencyTestruns TurtleLib'sLocaleFileChecker: every locale must matchen_US.ymlin keys and MiniMessage tags, parse strictly and be translated, anden_US.ymlmust hold exactly the keys the code uses.SpeciesConfigConsistencyTestchecks the built-in species defaults against the bundledconfig.yml; the legacy migration tests replay the 1.x upgrade against the files inlegacy/and older release files insrc/test/resources/release-*/. - Manual QA: stand up a Paper test server, copy the shaded jar, and exercise commands and modules. Felling, replanting and anything else a player does needs a real client.
-
Performance: use timings or Spark to verify large tree operations remain below target thresholds; adjust
leaves_decay.batch_sizedefaults cautiously.
-
version.propertiesholds the release version. If it is missing or has noversion, Gradle falls back to a-Pversion=...project property, and finally to1.0-SNAPSHOT. Commit it with every release sopaper-plugin.ymland binary names stay in sync. - Tag releases with exactly the version from
version.properties, without avprefix (e.g.2.0.0; 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 turtle-lib wiki's VersionComparator page, as some spellings still sort unexpectedly (e.g. dates). Existing tags stay as they are.
Release checklist:
- Update the changelog and
version.properties. - Archive older jars from
dist/first (e.g. intodist-archive/), since the build wipes it; then run./gradlew clean buildand make sure all tests pass. - 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). - Update README and wiki if user-facing behaviour changed.
- 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 and theivyrepository inbuild.gradlefor how it's fetched;exclusiveContentmakes that the only source for TurtleLib, and the Gradle wrapper checks its download against the official SHA-256). It is shaded into the jar, and its MIT license ships aslicenses/turtle-lib-MIT.txt. - If Gradle can't find TurtleLib, check that the pinned version actually has a matching tag and release asset (
turtle-lib-paper-<version>.jar) on turtle-lib. - Editing TurtleLib alongside Timberella: 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. - Shared helpers used today:
MessageService,UpdateChecker,ConfigWatcher,ConfigKeyMigrator,ConfigDefaultsInserter,LegacyDataUpgrade,FileChangeTracker,ServerMatcher,StartupBanner; in tests alsoLocaleFileChecker, whichbuild.gradleexcludes from the plugin jar.
- Language features: Java 25, prefer records/sealed classes only when they do not inflate bytecode for plugin targets.
-
Nullability: Use
Objects.requireNonNullfor constructor dependencies and explicit null checks for API inputs. -
Logging: Route player-facing output and console status lines through
MessageService(lang keys). PlaingetLogger()is only for technical diagnostics such as exception logs. -
MiniMessage: All user-visible strings live in
lang/*.yml. Do not inline MiniMessage strings in code. -
Configurability: Any new features must expose toggles/fallbacks in
config.ymlwith sane defaults. -
Other plugins' APIs (ItemsAdder, MMOItems, MythicCrucible, CraftEngine) are
compileOnlyand only touched from their adapter classes innet.kroet.timberella.compat, which load only when that plugin is enabled. -
Paper internals: Timberella fires its own events through constructors that Paper marks
@ApiStatus.Internal. InTreeChopListener, these are theBlockBreakEvent(asTimberBlockBreakEvent) for every further log inFelling.run()and for every leaf inscheduleLeavesDecay(), theBlockDropItemEventfor a further log's drops indropItems(), and theBlockPlaceEventinplaceSapling(). They are created and fired throughInternalEventGuard. If a Paper update breaks one of them (aLinkageErrorsuch asNoSuchMethodError), that log, leaf or sapling is left alone (a log's drops then fall without the drop event), and the console shows one warning per server run. Check these spots on every Paper build update, and keep new synthetic events behind the guard.
- Add keys to
src/main/resources/lang/en_US.ymlfirst. - Translate the keys in all other locales.
LocaleConsistencyTestfails on a value that is identical toen_USunless the key is on itsidenticalAllowedlist, so don't leave English placeholders behind. - Run the plugin once;
MessageServicesyncs new keys into deployedplugins/Timberella/lang/files without overwriting user customizations. - Keep placeholders consistent (
<prefix>,<prefix_label>,<player>, etc.). Add any new placeholder toPLACEHOLDERSinLocaleConsistencyTestand document it on the wiki Home page.
- 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.
- Parts of this plugin and its documentation were produced with AI assistance and reviewed by the maintainer before release; contributions are reviewed the same way, whoever or whatever wrote them.
Tests won’t run because Bukkit classes are missing. Unit tests may use server-independent Bukkit classes such as YamlConfiguration, which come from paper-api on the testImplementation classpath. Anything that needs a running server (worlds, blocks, players, the scheduler) needs lightweight stubs or a mocking library. Never spin up a server inside Gradle.
Shadow jar exploded to huge size — what changed? Check for accidental implementation scope dependencies (e.g., Paper API) or debug jars added to the runtime classpath. Only turtle-lib and bStats should be shaded — MiniMessage and Gson come in through the compileOnly Paper API, since Paper already provides them on the server classpath, and the custom-block plugin APIs are compileOnly as well.
How do I add a new config option safely? Add it to config.yml in snake_case, provide the same default in code, and document it on the wiki Home page. TimberellaPlugin merges missing keys into existing files without overwriting user values. When renaming a key, add an entry to LEGACY_CONFIG_KEY_MIGRATIONS (or LEGACY_LANG_KEY_MIGRATIONS) so existing files are rewritten in place.