Inter-plugin interfaces
Plugins talk through typed, versioned interfaces — methods as natives, events as forwards. A producer ctx.publishes an implementation; a consumer ctx.uses it.
// producer
import { plugin } from '@s2script/sdk/plugin';
export default plugin((ctx) => {
const zones = ctx.publish('@demo/zones', {
createZone(name: string, mins: number[], maxs: number[]) { /* … */ },
isInZone(slot: number, name: string): boolean { /* … */ return false; }
});
// forward an event to subscribers
ctx.server.onGameFrame(() => zones.emit('stay', { slot: 0, zone: 'spawn' }));
}); // consumer — types come from the producer's api.d.ts
import { plugin } from '@s2script/sdk/plugin';
import type { Zones } from '../../zones/api';
export default plugin((ctx) => {
const zones = ctx.use<Zones>('@demo/zones'); // hard dep — proxy throws while unpublished
zones.on('stay', ({ slot, zone }) => { /* … */ });
// ctx.tryUse<Zones>('@demo/zones') instead → Interface | null for an optional dep
}); A hard dep (ctx.use) returns a proxy that throws InterfaceUnavailable while the producer is unloaded; an optional dep (ctx.tryUse) resolves to Interface | null. Call args and event payloads cross contexts by structured copy — never a live pointer — and EntityRef round-trips as a live, liveness-gated ref.
Declare deps under s2script.pluginDependencies (or optionalPluginDependencies). Every import is ledgered, and unload walks reverse-dependency order.
See the interfaces module and the zones plugin.