Repository navigation
For‐Mod‐Developers
Other mods can hook into Apibalego by registering custom API events and entries to retrieve for their own usage. Kotlin is recommended for simplicity, but Java is also supported.
Usage examples are below.
| Object | Used for |
|---|---|
Apibalego |
Server api entries, server live update events |
ApibalegoClient |
Client api entries, client live update events |
LiveUpdatesEventRegistry / ClientLiveUpdatesEventRegistry
|
Websocket events, kotlin-only overloads included |
GamemasterCommand.onReloadHooks |
Run something on /gmaster reload
|
Java and Kotlin use the same objects: the registration methods are overloaded on the payload format
(see below), and the Codec/Class/json ones are static and free of Kotlin-only types, so java
mods can call them directly.
Everything must be registered at mod init, before the first poll happens. Registering the same key twice overwrites the previous handler.
Whatever you register, the payload (an entry's details, or a live event's message) can be read in
four ways:
| Format | How | Notes |
|---|---|---|
| kotlinx serializer | MyData.serializer() |
Kotlin only. The only one whose fields show up in generated docs |
Mojang Codec
|
MyData.CODEC |
Used by Minecraft in general |
| Plain class | MyData.class |
Uses gson, no need to write codec |
| Raw json | - | A gson JsonElement, parse it yourself |
Apibalego is meant as an optional dependency: check it is loaded before touching these classes (loader mod list check) to avoid Class not found exceptions.
// fabric
FabricLoader.getInstance().isModLoaded("apibalego")
// neoforge
ModList.get().isLoaded("apibalego")Keep every reference to Apibalego inside a class that is only touched behind that check, so the JVM never has to load it when the mod is missing:
/** Do not reference if Apibalego is not loaded. */
object MyModApibalego : ApiEntryHandler<MyEntryData> {
fun init() {
Apibalego.registerApiHandler(id("my_entry"), MyEntryData.serializer(), this)
}
override fun handleApiUpdate(server: MinecraftServer, entries: Collection<ApiEntry<MyEntryData>>) {
// ...
}
}
// in your mod init, isApibalegoLoaded being the loader check above
if (isApibalegoLoaded) MyModApibalego.init()Same in java:
/** Do not reference if Apibalego is not loaded. */
public class MyModApibalego {
public static void init() {
Apibalego.registerApiHandler(key, MyEntryData.CODEC, MyEntryData.class, (server, entries) -> {
// ...
});
}
}
// in your mod init
if (isApibalegoLoaded) MyModApibalego.init();Note
Remember Java classes are loaded only when they are referenced, not before
An api entry type is a key (Minecraft Identifier), a details payload format, and a handler. Handlers are
called on every poll cycle with all currently active entries of that type (not a diff), including if there are none,
and optionally when a player joins.
Apibalego.registerApiHandler(
id("my_entry"),
MyEntryData.serializer(),
object : ApiEntryHandler<MyEntryData> {
override fun handleApiUpdate(server: MinecraftServer, entries: Collection<ApiEntry<MyEntryData>>) {
// called on each poll cycle with all active entries of this type
}
override fun handleApiJoin(player: ServerPlayer, entries: Collection<ApiEntry<MyEntryData>>) {
// called when a player joins, with all currently active entries (optional)
}
}
)MyEntryData must be a @Serializable Kotlin class mapping to the details field of the entry.
Entries with no details at all:
Apibalego.registerSimple(
id("my_flag"),
{ server, entries -> },
{ player, entries -> }, // join handler, optional
)Same object, overloads that parse details with a Mojang Codec:
Apibalego.registerApiHandler(
Identifier.fromNamespaceAndPath("mymod", "my_entry"),
MyEntryData.CODEC,
MyEntryData.class,
new ApiEntryHandler<MyEntryData>() {
@Override
public void handleApiUpdate(MinecraftServer server, Collection<ApiEntry<MyEntryData>> entries) {
for (ApiEntry<MyEntryData> entry : entries) {
MyEntryData details = entry.getDetails();
}
}
@Override
public void handleApiJoin(ServerPlayer player, Collection<ApiEntry<MyEntryData>> entries) {
// optional, has a default no-op implementation
}
}
);Or skip the codec and let gson fill the class in:
Apibalego.registerApiHandler(key, MyEntryData.class, (server, entries) -> {
for (ApiEntry<MyEntryData> entry : entries) {
MyEntryData details = entry.getDetails();
}
});Or take the details tree as gson and read it yourself:
Apibalego.registerJsonApiHandler(key, (server, entries) -> {
for (ApiEntry<JsonElement> entry : entries) {
JsonElement details = entry.getDetails();
}
});Entries with no details:
Apibalego.registerSimple(key, (server, entries) -> {});
// with a join handler
Apibalego.registerSimple(key, (server, entries) -> {}, (player, entries) -> {});ApiEntry<T> carries id (stable id from the gamemaster, useful to dedup one-shot effects),
active, details (null for types registered without a payload format) and type.
Only active entries are dispatched. When an entry stops being sent, or is set inactive, it simply stops showing up in the collection - handlers that apply persistent effects need to undo them themselves.
Same model, but polled and handled entirely client-side (no world needed, so they also work from the main menu). There is no join hook.
Kotlin:
ApibalegoClient.registerApiHandler(id("my_entry"), MyEntryData.serializer()) { client, entries ->
// called on each client poll cycle with all active entries of this type
}
ApibalegoClient.registerSimple(id("my_flag")) { client, entries -> }Java:
ApibalegoClient.registerApiHandler(key, MyEntryData.CODEC, MyEntryData.class, (client, entries) -> {
for (ClientApiEntry<MyEntryData> entry : entries) {
MyEntryData details = entry.getDetails();
}
});
// gson-bound class instead of a codec
ApibalegoClient.registerApiHandler(key, MyEntryData.class, (client, entries) -> {});
ApibalegoClient.registerJsonApiHandler(key, (client, entries) -> {
// entry.getDetails() is a gson JsonElement
});
ApibalegoClient.registerSimple(key, (client, entries) -> {});Register these from the client init entrypoint of your mod.
Websocket events are matched by exact event name and dispatched as they arrive, instead of on a poll cycle. The response sender reports back to the gamemaster web server.
LiveUpdatesEventRegistry.register("my_event", MyData.serializer()) { data, server, sender ->
// handle the event
sender.sendSuccess() // or sender.sendFailure("reason")
}
// no payload
LiveUpdatesEventRegistry.register("my_event") { server, sender ->
sender.sendSuccess()
}The other payload formats are available too, if you'd rather not write a serializer:
LiveUpdatesEventRegistry.registerWithCodec("my_event", MyData.CODEC, MyData::class) { data, server, sender -> }
LiveUpdatesEventRegistry.registerWithClass("my_event", MyData::class) { data, server, sender -> }
LiveUpdatesEventRegistry.registerJson("my_event") { json, server, sender -> }Apibalego.registerLiveUpdateEvent("my_event", MyData.CODEC, MyData.class, (data, server, sender) -> {
sender.sendSuccess(Map.of()); // or sender.sendFailure("reason")
});
// gson-bound class instead of a codec
Apibalego.registerLiveUpdateEvent("my_event", MyData.class, (data, server, sender) -> {});
// raw gson JsonElement payload
Apibalego.registerJsonLiveUpdateEvent("my_event", (json, server, sender) -> {});
// no payload
Apibalego.registerSimpleLiveUpdateEvent("my_event", (server, sender) -> {});Client-side is the same through ClientLiveUpdatesEventRegistry / ApibalegoClient, with
Minecraft in place of MinecraftServer.
Run something whenever data is re-synced (/gmaster reload, or the reload live event):
GamemasterCommand.onReloadHooks.add { server -> }Enable generateApiDocs in the config to dump every registered entry type - Apibalego's
own and every consumer mod's - to apibalego-docs/ as markdown, on server start. Types registered
with a kotlinx serializer get their fields listed; codec, class and raw json payloads are listed by
name only, since their structure cannot be read at runtime.