Developers

Building from source

You need a JDK 21 or newer. Nothing else — Gradle downloads itself.

./gradlew build

The 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.yml and 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/ package

Each 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:

  1. The command class, under its module's command/ package.
  2. plugin.yml — name, description, usage. No permission and no aliases there.
  3. aliases.yml — a list, even if empty.
  4. 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.