Waiting for the game
setup runs as soon as your plugin's code loads, which is long before the game is ready. The HUD isn't drawn, the dictionary is empty, the colour palettes haven't loaded and the world extension doesn't answer. Code that acts then doesn't throw; it does nothing for the rest of the session.
List what your plugin needs in requires and Phantom waits for it before running setup:
import { definePlugin } from '@phantomclient/plugin';
export default definePlugin({
requires: ['game', 'hud'],
setup(ctx) {},
});What you can wait for
| Name | What it means | Permission |
|---|---|---|
game | The client is logged in and its world extension answers. Before this, a request can go quietly nowhere. | session |
room | The player is standing in a room. | none |
language | The client has its dictionary. Before this, every text id translates to an empty string. | localization |
hud | The main HUD is on screen. The client makes it once play starts. | gui |
interface | The client has its library art and colour palettes. Both arrive as the player logs in. | ui |
shops | The client has its item definitions. Before this, every item reads back as null. | shops |
Each one is checked through the SDK, so you need the permission for the area it reads. room needs none, because Phantom already knows which room each game is in. A manifest that requires something without the permission for it is refused, with the area named.
Declaring it
Put it in the manifest when the whole plugin needs it, so the player can be told before any of your code runs:
{
"name": "phantom-plugin-clothes-on-hud",
"version": "1.0.0",
"phantom": {
"id": "you.clothes-on-hud",
"title": "Clothes on the HUD",
"description": "Lists what your animal is wearing, on the HUD.",
"author": "You",
"requires": ["game", "hud"],
"permissions": { "game": "read" }
}
}Or put it in definePlugin, next to the code that needs it. If both have it, the two lists are combined.
Only an each-game plugin is held back. A single plugin sees every game at once, so there's no one game to wait for. A single plugin's manifest is refused if it declares requires; wait inside setup instead, naming the game.
Waiting inside setup
requires covers the whole plugin. Use ctx.ready for the rest: a switch the player turns on halfway through a session, one part of a plugin that needs the palettes, or a panel that should say the HUD isn't up yet.
await ctx.ready.wait(['language', 'shops'], { timeoutMs: 60_000 });
if (!(await ctx.ready.check('hud'))) ctx.log('no HUD yet');ctx.ready.wait(what, options) | Waits until the game has all of it. Rejects with NotReady, naming what's still missing, when timeoutMs runs out. Two minutes by default; Infinity waits as long as the frame lives. |
ctx.ready.check(what, options) | Whether the game has all of it now. Never throws. |
ctx.ready.missing(what, options) | What of it the game hasn't got. |
ctx.ready.waitingFor(reason) | Says what you're waiting for, in your own words. null takes it away. |
options takes game, which code not bound to a game must pass, and timeoutMs, which only wait reads.
When setup throws
If you only find out inside setup, and there's no name for what's missing, throw NotReady. Phantom runs setup again after one second, then two, then four, up to thirty seconds between tries.
import { definePlugin, NotReady } from '@phantomclient/plugin';
export default definePlugin({
requires: ['hud'],
async setup(ctx) {
const counter = await ctx.phantom().aj.hud.part('denCount');
if (!counter) throw new NotReady('the den counter is not on screen yet');
},
});Phantom also tries again by itself when something you require goes away during setup, for example when the player logs out.
Anything else setup throws is a crash. It's reported once and setup isn't run again.
What the player sees
While a plugin waits, its page in Plugins shows one line saying what it's waiting for. Its log says the same when it starts waiting and when it starts running. A command run meanwhile is refused with NOT_READY. To word that line yourself, use ctx.ready.waitingFor.
After setup
The client rebuilds the HUD for every room and builds each window when it opens, so something you attached to a button in setup is on the old button by the next room. Timing covers this: listeners kept through a rebuild, knowing when a room has finished building, and waits counted in frames.
Reading the client
If you need to wait for something Phantom has no name for, read the client's code. Phantom generates its TypeScript declarations from the client's ActionScript and keeps that ActionScript next to them. See Reading the client.