Developers
Building from source
You need a JDK 21 or newer. Nothing else — Gradle downloads itself.
./gradlew buildThe jar lands in build/libs/ChorusCore-<version>.jar, with bStats shaded into it under this
plugin's own package.
The -plain jar next to it is the same classes without bStats and is not the one to
install.
What the build does#
build also runs the checks in src/test/java. They:
- hold the config files and the code to a single command list —
plugin.yml,aliases.ymland the module files must agree exactly, and every command must have a rules block - check that every message key used in the code exists in
messages.yml, and that no key in the file is orphaned - check every shipped translation: real keys, valid MiniMessage, and every
%placeholder%present in the English line it replaces - exercise the SQL against a throwaway SQLite file
A file added without its entry in one of those lists fails the build rather than a server.
The three passes#
| Task | What it proves |
|---|---|
gradlew build |
It compiles against the Paper 1.18.2 API and emits Java 17 bytecode, and the test suite passes |
gradlew apicheck |
The same sources still compile against the newest Paper API, so nothing used here has been removed |
gradlew foliacheck |
Every Folia scheduler signature the reflection bridge asks for still exists |
apicheck runs foliacheck after itself.
foliacheck is what stops a renamed Folia method reaching a server. Those classes are reached
by reflection because they do not exist in the 1.18.2 API, which means no compiler can check
them — so they are resolved against the newest Paper API in a test instead.
Why 1.18.2#
The jar is compiled against the Paper 1.18.2 API and emits Java 17 bytecode, which is what lets one jar run on 1.18.2 through 26.2.
The cost is that every API used has to exist across that whole range. Several Bukkit types were
renamed along the way — Sound became an interface, some Attribute constants changed — so
anything new is checked against both API jars before it is used.
Layout#
src/main/java/dev/chorus/core/
├── api/ the public API — the only stable surface
├── command/ the command framework: rules, cooldowns, confirmations, aliases
├── config/ config file handling
├── storage/ the database, the pool, the migrations
├── platform/ the scheduler bridge, including Folia
├── locale/ messages and translations
├── menu/ the menu framework
├── papi/ the PlaceholderAPI expansion
├── importer/ the EssentialsX and QuickShop importers
└── <module>/ one package per module, each with its own command/ packageEach module is a ChorusModule: it owns its config file, registers its own commands and can be
reloaded on its own.
Adding a command#
Four places have to agree, and the tests enforce it:
- The command class, under its module's
command/package. plugin.yml— name, description, usage. No permission and no aliases there.aliases.yml— a list, even if empty.- The module's config file — a block under
commands:, even if empty.
Plus its permission node in the permissions: section of plugin.yml, and its messages in
messages.yml.
Adding a database column#
Never edit an existing CREATE TABLE. It will not run on a server whose table already
exists.
Add an ALTER step to the end of that table's list in its schema. A server upgrading runs only
the steps it has not seen, and chorus_schema_version records which.
Style#
The code is written to be read by somebody who did not write it. Comments say what a thing is and what it is for, not why one design was chosen over another. There are no emoji anywhere in the source or the messages.
License#
MIT.
