-
Notifications
You must be signed in to change notification settings - Fork 0
First Plugin
This walkthrough creates a Paper plugin that logs lifecycle changes, observes joins through the current data-only event callback, and registers /shamoo-hello. It deliberately uses an exported class with no constructor dependencies because that is what the current bundled adapter can instantiate.
Complete source for a comparable minimal project is available in examples/hello-world. The richer examples/complete-paper-plugin demonstrates more declarations, but some generated JVM-typed event operations exceed the current data-only adapter.
Run the CLI from an environment where all v0.1.0-rc.1 release tarballs were installed together as described in Installation:
pnpm exec shamoo create first-shamoo-plugin \
--name @example/first-shamoo-plugin \
--platform paper
cd first-shamoo-plugincreate does not run a package installer. The generated version ranges describe package compatibility but are not a claim that npm hosts those packages. Install all downloaded GitHub release tarballs into this project together:
pnpm add /absolute/path/to/shamoo-ts-v0.1.0-rc.1/*.tgzThe project needs @shamoo/decorators, the Paper facade, the generated Paper raw event declarations, the CLI, and TypeScript. A concise package.json is:
{
"name": "@example/first-shamoo-plugin",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"build": "shamoo build",
"deploy": "shamoo deploy",
"dev": "shamoo dev",
"doctor": "shamoo doctor",
"typecheck": "tsc --project tsconfig.json --pretty false"
},
"dependencies": {
"@shamoo/decorators": "0.1.0-rc.1",
"@shamoo/paper": "0.1.0-rc.1",
"@shamoo/paper-raw": "0.1.0-rc.1"
},
"devDependencies": {
"@shamoo/cli": "0.1.0-rc.1",
"typescript": "5.8.3"
}
}Those versions identify the release. With the current distribution, the lockfile and usually the manifest must resolve them to the local .tgz files installed in the previous step. Do not replace them with registry fetches unless npm view <package>@0.1.0-rc.1 version has independently confirmed a deliberate publication.
Parameter decorators require legacy decorator syntax because the current standard decorator proposal has no parameter decorators. This example has no parameter decorators, but enabling the option keeps the project ready for them:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"noEmit": true,
"experimentalDecorators": true,
"skipLibCheck": true
},
"include": ["src/**/*.ts"]
}Shamoo does not use emitDecoratorMetadata; leave it off.
Create shamoo.config.json:
{
"name": "@example/first-shamoo-plugin",
"displayName": "First Shamoo Plugin",
"version": "0.1.0",
"platforms": ["paper"],
"entrypoint": "src/plugin.ts",
"paperEntrypoint": "src/paper.ts",
"tsconfig": "tsconfig.json",
"outDir": "dist",
"permissions": {
"builtins": [],
"filesystem": { "read": [], "write": [] },
"network": false,
"workers": false,
"childProcess": false,
"nativeAddons": false
},
"communication": {
"services": [],
"events": [],
"consumers": []
},
"deploy": {
"paper": "/srv/paper/plugins/ShamooRuntime/plugins"
}
}Change deploy.paper to the actual ShamooRuntime watched root. The default is <paper-server>/plugins/ShamooRuntime/plugins, not <paper-server>/plugins.
Create src/plugin.ts:
import { Command, Context, OnDisable, OnEnable, Plugin } from '@shamoo/decorators';
import type { PaperCommandContext } from '@shamoo/paper';
import { OnPlayerJoinEvent } from '@shamoo/paper-raw';
interface PaperEventPayload {
readonly type: string;
readonly asynchronous: boolean;
}
@Plugin({ name: 'first-shamoo-plugin' })
export class FirstShamooPlugin {
private joins = 0;
@OnEnable()
public enable(): void {
console.info('[first-shamoo-plugin] enabled');
}
@OnPlayerJoinEvent()
public joined(@Context() event: PaperEventPayload): void {
this.joins += 1;
console.info(
`[first-shamoo-plugin] ${event.type}; joins=${String(this.joins)}`,
);
}
@Command('shamoo-hello')
public hello(@Context() context: PaperCommandContext): boolean {
return context.reply(`Hello ${context.sender.name}; observed joins=${String(this.joins)}`);
}
@OnDisable()
public disable(): void {
console.info('[first-shamoo-plugin] disabled');
}
}Why the event parameter is not typed as generated PlayerJoinEvent: the generated declaration accurately describes the pinned Paper API at compile time, but the current Runtime callback intentionally crosses only copied data. It supplies { type, asynchronous }, not a live Bukkit object. Calling event.getPlayer() in this deployed path would be incorrect. The command path has a supported data-only PaperCommandContext with reply, player lookup, and main-hand operations.
Create src/paper.ts:
export * from './plugin.js';
import { definePaperEntrypoint } from '@shamoo/paper';
export default definePaperEntrypoint({
enable() {
console.info('[first-shamoo-plugin] Paper bundle enabled');
},
disable() {
console.info('[first-shamoo-plugin] Paper bundle disabled');
},
});Although the public entrypoint type accepts PaperEntrypointContext, the current bundled adapter invokes entrypoint hooks with no arguments. Use zero-argument hooks for deployed code.
pnpm typecheck
pnpm doctor
pnpm build
pnpm deployThe build writes:
dist/paper/index.js
dist/paper/index.js.map
dist/shamoo.metadata.json
Deployment writes an installation similar to:
/srv/paper/plugins/ShamooRuntime/plugins/first-shamoo-plugin/
├── paper/index.js
├── paper/index.js.map
├── shamoo-plugin.json
└── shamoo.metadata.json
ShamooRuntime waits for a stable file inventory, stages the candidate, validates descriptor and compatibility data, and then loads or replaces the generation. Watch the Paper log for the Runtime initialization message and the plugin's enable messages. Run /shamoo-hello from a player or console.
For active development:
pnpm devThis is complete generation rebuild/redeployment, not in-place module hot replacement.
Create or add src/velocity.ts, set platforms to ["paper", "velocity"], set velocityEntrypoint, and configure both deploy roots. Keep src/plugin.ts platform-neutral; imports reachable only from src/paper.ts may use @shamoo/paper*, while imports reachable only from src/velocity.ts may use @shamoo/velocity*. The compiler rejects opposite-platform imports and the bundler creates independent artifacts. See the dual-platform example.