Developer API
TTSCore publishes a small contract so a plugin can ask the server what it can do and change its settings while it runs. DominionForge uses it for its server page.
One rule comes first: a plugin must work in full on plain Paper. TTSCore unlocks what a plugin cannot reach. It is never a requirement.
What is in it
Package dev.ttsstudio.ttscore.api:
| Class | Purpose |
|---|---|
TTSCoreProvider |
The entry point: get(), isPresent(), and the contract version |
TTSCoreApi |
What this server is: name, build, capabilities, settings |
Capabilities |
Identifiers for "this server can do X" |
Settings |
The catalogue of settings, with read and write |
Setting |
One setting: type, limits, default and description |
SettingType, SettingScope |
Its type, and whether it is global or per world |
Result |
How a write went, with a message you can show a player |
Detecting TTSCore without breaking on Paper
On plain Paper these classes do not exist. Naming one in a class that always loads ends in NoClassDefFoundError at startup. Keep everything that touches TTSCore in its own class and create it only when the brand matches:
// Always loaded. Names nothing from TTSCore.
static boolean serverIsTTSCore() {
try {
return "ttsstudio:ttscore".equals(
io.papermc.paper.ServerBuildInfo.buildInfo().brandId().asString());
} catch (Throwable ignored) {
return false;
}
}
// In onEnable. TTSCoreBridge is loaded on this line and only if needed.
hook = serverIsTTSCore() ? new TTSCoreBridge() : null;
"TTSCore".equals(Bukkit.getName()) works too. TTSCoreProvider.isPresent() does not, because calling it already loads the class.
Ask what it can do, not which version it is
TTSCoreApi core = TTSCoreProvider.get();
if (core.apiVersion() < TTSCoreProvider.API_VERSION) return;
if (core.has(Capabilities.RAID_TNT_FUSE)) {
core.settings().set(world, "raid.tnt.fuse-ticks", 20);
}
A published capability is never removed and never changes meaning.
The catalogue
settings().all() returns every setting with its type, limits, default and the same comment that is written in ttscore.yml. A menu can draw itself from it:
for (Setting s : core.settings().all()) {
// s.type(), s.min(), s.max(), s.label(), s.description(), s.scope()
}
A setting added to the server appears in your menu without recompiling the plugin.
Reading and writing
Settings s = core.settings();
s.getInt(world, "raid.tnt.fuse-ticks"); // the value in force there
s.isOverridden(world, "raid.tnt.fuse-ticks"); // its own, or inherited?
Result r = s.set(world, "raid.tnt.fuse-ticks", 20);
if (!r.success()) player.sendMessage(r.message());
s.unset(world, "raid.tnt.fuse-ticks"); // inherit again
s.set(Settings.DEFAULT_WORLD, "raid.tnt.fuse-ticks", 40);
- Every write is checked against the catalogue for type and range. A value out of range is rejected, not clamped.
- Writes belong to the main thread.
set,unsetandreloadreturn a failedResultfrom any other thread. Reads are safe anywhere. - Each write saves the file and reads it back, so disk and memory never disagree.
Compiling against it
Depend on the contract with scope provided. At runtime the server supplies the classes.
<dependency>
<groupId>dev.ttsstudio</groupId>
<artifactId>ttscore-plugin-api</artifactId>
<version>1.21.11-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
The artifact is available to TTSCore owners. Ask for it in a ticket on our Discord.
Versioning
TTSCoreProvider.API_VERSION goes up only on an incompatible change. New settings and capabilities do not move it. A plugin compiled against version 1 keeps working on any TTSCore that announces version 1.