The API
A plugin is two files: a plugin.json saying where
the code is, and an entry file whose default export is
activate(api). The editor calls it once when the
plugin is switched on and hands over api.
Through it you read the open map and write to it, add to the
chrome (menu and context items, hotkeys, dialogs, floating
panels, map tools that own the pointer, overlays drawn on the
canvas), and read the game's own decoded
.dat tables. Anything you register is automatically
taken away when the plugin is switched off.
Every write goes through
api.document.edit(label, tx => …). Whatever the
callback does becomes one undo entry, marks the CHK
sections it touched, and repaints. The plugin's edit then undoes
and redoes like any other stroke.
The API is promise-first. Anything asynchronous returns a
Promise, and activate and a dialog
button's run may be async. A dismissal
resolves to null or false; it does not
reject. The exception is the callback you hand to edit. It
commits the moment the callback returns, so an
async one would commit only what ran before its
first await, and the rest would write outside the
entry. Await the graphics, the map pick or the fetch
first, then write in one go. TypeScript refuses an
async builder, and a plain-JavaScript one is caught at run time.
export default function activate(api) {
api.menu.add("Tools", {
label: "Marine here…",
enabled: () => api.document.isOpen(),
async run() {
// Asynchronous, so await it. Esc or a right-click resolves null.
const at = await api.ui.pickTile({ prompt: "Where does the marine go?" });
if (!at) return;
// Synchronous, so the whole thing is one undo entry called "Place a marine".
api.document.edit("Place a marine", (tx) => {
const px = api.consts.TILE_PX;
tx.placeUnit(0, 0, at.x * px + px / 2, at.y * px + px / 2);
});
},
});
}
TypeScript is transpiled in the browser, so a one-file plugin needs no toolchain. A repository that wants npm dependencies commits a bundle and names it in the manifest.