The game client
phantom.flashThe game client
The game client itself, for what the SDK does not wrap: every public class, object, property and method in it, typed from the client's own declarations, and what runs inside Flash for scripts: hooks, watches, the overlay, highlights, keys and the view.
const avatars = phantom.flash.use('avatar.AvatarManager');
const name = await avatars.playerAvatar.userName.$get();
await avatars.playerAvatar.setColors(1, 2, 3);
const main = phantom.flash.global('gMainFrame');
console.log(await main.userInfo.myUserName.$get());Everything that subscribes resolves with a function that ends it (hook, override, watch, onTick, onKey, highlight, hide, recolor, style, the overlay's nodes and widgets), and all of it is ended in Flash when the bridge closes.
Scripts and plugins
A script has all of it, as phantom.flash, and so does a plugin, from ctx.phantom(): the SDK runs in the plugin's own frame. Each message it sends is checked by what it does: reading, hooking and drawing need "game": "read" in the manifest, and calling, setting, overriding and driving need "game": "act" (see Permissions).
Overview
| Member | What it does | Plugins |
|---|---|---|
rules | What hook rules are made of: const { arg, equals } = phantom.flash.rules. | |
answering() | A function of your own that the game can call, and that answers it. | |
chain() | A few steps run one after another in the game, inside one frame, each able to use what the one before it answered. | |
client() | The game client's document object. | |
collect() | The same members read out of every item of a list in the game, in one answer. | |
describe() | What one of the client's classes holds: its members, their types, and the names to hook them by. | |
drive() | Holds values and calls the game's own methods together, in one frame, inside the game. | |
find() | The game's live objects, found by what they are rather than by where they are. | |
frames() | Waits for count frames of the game, and resolves with the frame it stopped on. | |
global() | A package-level variable or constant, by qualified name, such as gMainFrame. | |
hide() | Hides a display object until the function it gives back is called, even if the game shows it meanwhile. | |
highlight() | Glows or recolours one of the game's display objects (an avatar's view, an NPC, a room object) until the function it gives back is called. | |
hold() | Keeps a value where the game keeps putting its own back, frame after frame. | |
holdAll() | Keeps several values in place together, in the same frame as each other. | |
hook() | Hooks one of the client's methods, getters or setters: rules applied inside the call, and a listener told of calls afterwards. | |
hooks() | The hooks Phantom's rewrite of the client put in, by name. | |
inspect() | A display object and everything under it, as plain data: each object's name, class, whether it is shown, its position, scale and size, its timeline (frame, label, labels), a text field's text, whether its parent's timeline placed it, and where it is drawn, in a few messages however big the tree is (one for the root and one per level). | |
nextFrame() | Waits for the next frame of the game. | |
notes() | Notes of your own, kept beside the game's own objects. | |
override() | Replaces one of the client's methods with handler, which may run the original for the call (call.original(), with the same arguments or others) or not. | |
recolor() | Changes the colours of the whole game, or of one display object, until the function it gives back is called: recolor('grayscale'), recolor({ hue: 90 }), recolor({ tint: 0xff0000, amount: 0.3 }). | |
repeat() | Has the game call one of its own methods, frame after frame, on its own. | |
search() | Finds a method, getter or setter anywhere in the client by part of its name. | |
snapshot() | One of the game's own display objects, drawn to a picture. | |
stage() | Flash's stage, which the game is drawn on. | |
style() | Styles one of the client's text fields (font, size, colour, background, border, outline) until the function it gives back is called, which puts its look back. | |
track() | Follows every instance of one of the game's classes, as they come and go. | |
until() | Waits until a place in the game holds something, or until test says its value will do. | |
use() | A class, by qualified name: its static members, and $new to make one. | |
watch() | Follows values in Flash: read there every few frames, and told here only when one changed. | |
when() | Waits for a condition inside the game, and reads the places you name in the frame it held. | |
whenever() | Runs attach on an object of the client's each time the client makes it, and takes off what it put on when the object goes. | |
whenReady() | Resolves once the game's loader is running. | |
onKey() | Hears a keyboard shortcut in the game: onKey('Ctrl+Shift+H', ...). | |
onReload() | Hears the game's page load the client, which is the moment everything in Flash has to be put back. | |
onTick() | Hears every everyFramesth frame of the game (every frame by default), with its number and time. |
flash.overlay: Drawing above the game.
| Member | What it does | Plugins |
|---|---|---|
attach() | Draws node inside one of the client's containers, above its children, so it moves, scales and hides with it; x and y are then in the container's coordinates. | |
button() | Draws a button with a label. | |
can() | Whether the loader in the game can do something, by the name it lists the feature under. | |
checkbox() | A box the player ticks, with words beside it. | |
circle() | Draws a circle around its position. | |
clear() | Removes everything this overlay drew. | |
column() | Lays nodes out in a line down the screen, one under the next, and says what the line takes up. | |
draw() | Draws a node of any type, by name. | |
focusGame() | Gives the keyboard back to the game, from an input that had it. | |
group() | Draws an empty group, to hold other nodes and move, hide or remove them together. | |
image() | Draws an image from the content origin, or a base64 data: URL. | |
input() | A text input. | |
inspect() | A node and everything drawn in it: where each was put, the size it was drawn with and where Flash has it now. | |
line() | Draws a straight line from the node's position to toX, toY. | |
list() | Draws rows of rich text in a scrolling box; rows and the links in them are clickable. | |
marker() | Something drawn at a place in the room rather than on the screen: a marker over a chest, an arrow at a doorway, a label on a spot. | |
measure() | How big text would be drawn, without drawing it. | |
panel() | A box with a title bar that holds other nodes (drawn with it as their parent, placed from its top left corner below the title bar, and clipped to it). | |
place() | Puts each node where the matching point says, measuring it first so that its box lands there. | |
rect() | Draws a rectangle, filled, outlined or both. | |
replace() | Hides one of the client's display objects and draws node in its place: in the same parent, just above it, at its position and (unless the node has its own) its size. | |
rich() | Draws rich text: spans, or markup such as [b][color=#ff9900]sxip[/color][/b]: hi (parseMarkup). | |
row() | Lays nodes out in a line across, one after the next, and says what the line takes up. | |
scroller() | A box that scrolls whatever is drawn in it, under the wheel. | |
select() | A dropdown: a button showing what is chosen, which opens a list of the rest. | |
slider() | A slider: a track, and a knob the player drags along it. | |
tabs() | A row of tabs, each with a group of its own that is shown while its tab is. | |
text() | Draws plain text, in one font, at a point on the stage or following an anchor. | |
world() | The layer the room is drawn in, as a place to anchor to: the player's avatar's own parent, whose coordinates are the ones phantom.aj.player.avatar.position() and an adventure's actor positions are in. |
flash.ui: Rules over the game's own screen: hide, restyle or change every object of a class.
| Member | What it does | Plugins |
|---|---|---|
rule() | Applies apply to every object of a class the game draws, now and as more appear, and resolves with the running rule. |
flash.bridge: The untyped bridge underneath, for anything the declarations do not describe.
| Member | What it does | Plugins |
|---|---|---|
client | The client the loader loaded, once it has said. | |
features | What the loader said it can do beyond version 1 (BRIDGE_FEATURES). | |
reloads | How many times the loader has started over under this bridge: 0 until the page has been loaded again. | |
state | Where the bridge is: waiting for the loader to say it runs, ready, or closed for good. | |
allocate() | A number of this bridge's own, for something it will name in Flash (an overlay node). | |
answer() | What a function you passed into Flash gives the game back when the game calls it. | |
bounds() | Where an overlay node is now and how big it is, children and all. | |
chain() | A few steps run one after another in the game, in one frame, each able to use what the one before it answered. | |
close() | Stops the bridge: every waiting request fails, and nothing more is heard. | |
collect() | The same members read out of every item of a list in Flash, in one answer. | |
construct() | Makes an object of one of Flash's or the client's classes, and resolves with a handle to it. | |
decode() | Turns what crossed the bridge into a value: handles become FlashHandles. | |
definition() | A class, or a package-level function or variable, by qualified name. | |
draw() | Makes or changes overlay node node. | |
drive() | Values Flash holds in place itself, frame after frame. | |
encode() | Turns an argument into what crosses the bridge. | |
encodeRequest() | A request encoded as it would be sent, with a number of this bridge's own, but not sent. | |
erase() | Removes overlay nodes, with what is drawn in them. | |
find() | The live objects under a place, found by what they are rather than by where they are. | |
gameClient() | The game client's document object. | |
highlight() | Puts effect on the display object at entry. | |
hook() | Registers hook hook with rules. | |
hookTable() | The hook table, loaded by the loader from url, or from where the page's flashvars say. | |
invokeOriginal() | Runs the original method for an overridden call, with its own arguments or with args, and resolves with what it returned. | |
listen() | Listens for an event on an event dispatcher in Flash, and resolves with what stops listening. | |
measure() | How big text would be drawn, without drawing it. | |
note() | One table of notes: data of your own, kept in Flash beside the game's own objects. | |
overrideDone() | Lets go of what Flash holds for an overridden call. | |
release() | Lets Flash forget objects it handed over as handles, so they can be collected. | |
render() | Holds render settings on the tree under a place: stage.quality, and smoothing, pixelSnapping and cacheAsBitmap on everything found under it. | |
renderStats() | What render hold render has done: how much of the tree the last walk looked at, how much it is holding, and whether it stopped at maxObjects before the tree ran out. | |
request() | Sends a request and resolves with its value. | |
shader() | Starts a Stage3D pass over the game: a shader of your own, running on the graphics card. | |
shaderStats() | What shader pass shader is doing and what it is costing, read now. | |
snapshot() | One of the game's display objects, drawn to a picture. | |
stage() | Flash's stage, which the game is drawn on. | |
style() | Styles the text field at entry. | |
subscribe() | Starts something in Flash that posts messages under a number (a hook, a watch, a key), and resolves with what stops it. | |
tick() | Hears every everyFramesth frame. | |
track() | A live list of every instance of a class, kept current in the game. | |
vector() | Makes a Vector.<Item> holding those items, and resolves with a handle to it. | |
watch() | Reads entries every everyFrames frames in Flash, and hears their values when one changed. | |
when() | Waits in the game until a condition holds, and answers what it read in the frame it held. | |
whenReady() | Resolves once the loader has said it is running. | |
onClient() | Hears whether the game client loaded, or why not. | |
onKey() | Hears a key at the stage. | |
onReloadLoss() | Hears what the bridge could not put back after the game's page loaded the client again. | |
onSubscription() | Hears a message a subscription made some other way posts (an overlay node's), until the function it gives back is called. | |
onUncaught() | Hears errors the game client threw and nothing caught. |
Properties
rules
rules: { self: (...path: (string | string[])[]) => HookRead; arg: (arg: string | number, ...path: (string | string[])[]) => HookRead; returned: (...path: (string | string[])[]) => HookRead; fromClass: (name: string, ...path: (string | string[])[]) => HookRead; place: (place: unknown, ...path: (string | string[])[]) => HookRead; calls: () => HookRead; item: (...path: (string | string[])[]) => HookRead; constant: (value: unknown) => HookRead; equals: (left: unknown, right: unknown) => HookTest; notEquals: (left: unknown, right: unknown) => HookTest; less: (left: unknown, right: unknown) => HookTest; lessOrEqual: (left: unknown, right: unknown) => HookTest; greater: (left: unknown, right: unknown) => HookTest; greaterOrEqual: (left: unknown, right: unknown) => HookTest; contains: (left: unknown, part: unknown) => HookTest; startsWith: (left: unknown, start: unknown) => HookTest; endsWith: (left: unknown, end: unknown) => HookTest; oneOf: (left: unknown, list: unknown) => HookTest; exists: (value: unknown) => HookTest; changed: (value: unknown) => HookTest; all: (...conditions: HookWhen[]) => HookTest; any: (...conditions: HookWhen[]) => HookTest; not: (condition: HookWhen) => HookTest; }What hook rules are made of: const { arg, equals } = phantom.flash.rules.
Hook and override rules run inside the call in Flash, where TypeScript cannot, so they are described with these builders rather than written as functions. Values: arg(name or index, ...path), self(...path) on the object the method runs on, returned(...path) at exit, fromClass(className, ...path), place(place, ...path), calls(), constant(value) and item(...path) for the thing a rule with no call is about. Conditions: equals, notEquals, less, lessOrEqual, greater, greaterOrEqual, contains, startsWith, endsWith, oneOf, exists, changed, and all, any and not to combine them. They go in a hook's when, skip, replaceArgs and replaceReturn, an override's when, replaceArgs and returns, a held value's while, a collect's where, a chain step's if and a wait's condition. An argument named that the method does not have throws as the hook is made.
Two of them belong to one kind of rule each. item is the item a collect's where is testing, or the step before this one in a chain's if, and a rule with a call of its own refuses it. changed holds for a value that differs from what that comparison itself last read, so it belongs to a rule read again and again, and a collect and a chain, which read each thing once, refuse it.
Types
Example
const { arg, fromClass, equals, not } = phantom.flash.rules;
const me = fromClass('avatar.AvatarManager', 'playerSfsUserId');
const stop = await phantom.flash.hook('avatar.AvatarManager.addAvatarMessage', {
when: not(equals(arg('userId'), me)),
skip: true,
});Methods
answering
answering<Value extends FlashData = FlashData>(listener: (...args: FlashValue[]) => void, answer?: Value | undefined) => FlashFunction<Value>A function of your own that the game can call, and that answers it.
The game calls from inside its own code, which cannot wait for TypeScript. So the answer is kept in the game and read at every call: set it before the game calls, change it between calls, and listener still hears each call as it happens, a moment later. That is how a comparator, a filter or a test of your own goes where the game expects one. Hand argument to the game.
Parameters
| Name | Type | |
|---|---|---|
listener | (...args: FlashValue[]) => void | Called with each call from the game, after it has happened. |
answer optional | Value | undefined | What the game reads from the call until it is set again. Nothing, when left out. |
Returns
FlashFunction<Value>. The function: argument to hand over, answers, answersNothing and stop.
Types
Example
const replies = phantom.flash.answering<boolean>((command) => console.log('the game heard', command), true);
await phantom.flash.use('XtReplyDemuxer').addModule(replies.argument, 'gl');
await replies.answers(false);chain
chain(steps: ChainSteps) => Promise<Chained>A few steps run one after another in the game, inside one frame, each able to use what the one before it answered.
It is the answer to the shape most of this SDK is made of: ask the client for something, then read members off the object it handed back. That is a call, a read for each member and a release, with the object crossing as a handle in between, so one definition costs three messages and a list of ten costs twenty-three. The steps go over together here and the object never leaves Flash, so the same work is one message and nothing to release.
A step works on a place, as any request does, or on step(n) for what an earlier step answered, where a negative n counts back from the step naming it. step(n).at('member') goes further along from it, and step(n) may also stand anywhere a value goes, a list's item and an object's field included. The work is one of get, set, call, make (a new object of one of the game's classes) and collect.
Nothing crosses unless a step says hold: true, which hands that step's answer over as a place held by handle; without it an object is named by its class and stays in Flash. And nothing of the game's happens between two steps, so a value one step writes is the value the next step's call reads, and an object the client would have thrown away before a second message arrived is still the object the next step reads.
A step may carry an if, read before it runs, whose rules.item is the step before it. Where it does not hold the chain stops there and stopped says which step that was, which is how "open the window, then press the button only if it is the right window" is one message.
Parameters
| Name | Type | |
|---|---|---|
steps | ChainSteps | The steps in order, at most 32, or a function handed step to name earlier steps by. |
Returns
Promise<Chained>. Each step's value in order, stopped for the step an if stopped the chain at, and collected(n) for the rows a collect step read.
Types
ChainSteps · ChainStepSpec · ChainStepRef · Chained
Example
const avatars = phantom.flash.use('avatar.AvatarManager');
const chained = await phantom.flash.chain((step) => [
{ call: avatars.getAvatarByUserName, args: ['Sxip'] },
{ collect: step(-1).at('avatarWorldView'), fields: { x: 'x', y: 'y' } },
]);
console.log('one message, not three:', chained.collected(1).rows[0]);client
client() => TypedFlash<AJClient>The game client's document object.
The AJClient sprite the loader loaded: the top of the game's own display list, below the stage and the loader. Most of the game is easier reached from global('gMainFrame') or a manager class through use.
Returns
TypedFlash<AJClient>. The client as a typed place (AJClient).
Types
Example
const client = phantom.flash.client();
console.log('children:', await client.numChildren.$get());collect
collect(place: FlashPlaceLike, fields: CollectFields, options?: CollectOptions) => Promise<Collected>The same members read out of every item of a list in the game, in one answer.
Reading a room's players one member at a time is a message each. This reads the whole list in the game and answers it together, which is what a table, a scan or a count wants. The place is an array, a vector, a dictionary, a display object (its children), or a single object, which is read as a list of one.
A member that is not there, or that throws, comes back as null rather than failing the read.
where and sortBy decide, in the game, which items are worth reading at all and in what order, before a single row is built. "The nearest animal I could click" is then one message and one row rather than forty rows and a sort here. rules.item is the item being tested, and every other rule is there as usual.
Parameters
| Name | Type | |
|---|---|---|
place | FlashPlaceLike | The list to read: a typed place, or a place read from the game. |
fields | CollectFields | The members to read: a list of paths, or names of your own to read them under. |
options optional | CollectOptions | where, the items worth reading; sortBy, the member to order them by; skip, limit and handles. |
Returns
Promise<Collected>. The rows read, how many items a where left (total), and how long the list itself was (scanned).
Types
CollectFields · CollectOptions · Collected · FlashPlaceLike
Example 1
const players = phantom.flash.use('avatar.AvatarManager').avatarList;
const { rows, total } = await phantom.flash.collect(players, {
name: 'userName',
x: 'avatarWorldView.x',
});
console.log(`${rows.length} of ${total}`, rows[0]?.name);Example 2
const { item, exists } = phantom.flash.rules;
const players = phantom.flash.use('avatar.AvatarManager').avatarList;
const { rows, total, scanned } = await phantom.flash.collect(
players,
{ name: 'userName', x: 'avatarWorldView.x' },
{ where: exists(item('avatarWorldView')), sortBy: 'avatarWorldView.x', limit: 1 }
);
console.log(`the leftmost of ${total} drawn animals, out of ${scanned} in the list`, rows[0]?.name);describe
describe(what: string | FlashPlaceLike, options?: DescribeOptions) => Promise<ClientDescription>What one of the client's classes holds: its members, their types, and the names to hook them by.
The first question anyone writing against the client has, answered without leaving Phantom. Two sources are merged, because neither is enough on its own. The hook table names every method, getter and setter, with the parameter names Animal Jam's own source gives them and the name to pass hook and override. The running game, asked through describeType, lists the plain fields, which have no hook because there is no call to catch.
Name a class, or give a place, in which case the game is asked what the object there really is and the answer is that class's.
What it cannot tell you is what the client does with a member: that a field is written with htmlText += and never escaped, or that it carries a stylesheet which makes <font> do nothing. That is in the client's own ActionScript, and Reading the client says how to get it.
Parameters
| Name | Type | |
|---|---|---|
what | string | FlashPlaceLike | The class's qualified name (gui.ChatHistory), or a place in the game. |
options optional | DescribeOptions | live: ask the running game as well as the table, true by default. |
Returns
Promise<ClientDescription>. The class, its base, and its members: the constructor, then objects' members, then the statics, each by name.
Types
Example
const chat = await phantom.flash.describe('gui.ChatHistory');
for (const member of chat.members) {
console.log(member.kind, member.name, member.type, member.hooks.join(' '));
}drive
drive(options?: DriveOptions) => Promise<Unsubscribe>Holds values and calls the game's own methods together, in one frame, inside the game.
hold, holdAll and repeat are the short forms of this, and each reaches half of what the loader's drive can do (Drives.as). The whole of it is one job: on the frames it names, a drive writes its values, and then calls the methods it was given, with nothing crossing the bridge for any of it. Writing and then calling in that order, in the same frame, is what a step the client takes from a value it reads needs: set where the animal is going, then have the client walk it, before the client gets a frame of its own in between.
Up to 32 values and 32 calls. while decides in the game whether the frame is one to act on, once acts on the first frame it holds and then stops, and restore (on by default) puts back what the first write found. A write or a call whose object is not there yet is left for the next frame, and the rest go ahead, so a drive survives a room change and takes up the new room's objects by itself. Only a write or a call the game refuses turns it off, which onFailed hears; left out, that is logged, since a drive that quietly stopped is the hardest kind of fault to find.
Parameters
| Name | Type | |
|---|---|---|
options optional | DriveOptions | hold, the values to write; call, the methods to call; everyFrames, while, once, restore and onFailed. |
Returns
Promise<Unsubscribe>. A function that ends the drive, putting back what it found.
Types
Example
const { exists, place } = phantom.flash.rules;
const room = phantom.flash.use('room.RoomManagerWorld').instance;
const walking = await phantom.flash.drive({
hold: [{ place: room.$at('_pathSpeed'), value: 2 }],
call: [{ method: room.movePlayerOnPath }],
while: exists(place(room, '_followPath')),
});find
find<Options extends FindOptions>(options?: Options | undefined, where?: FlashPlaceLike | undefined) => Promise<FoundPlaces<Options["ofClass"]>>The game's live objects, found by what they are rather than by where they are.
Reflection reaches what a path names, which means knowing the path. A screen the game just built, a button inside a list it filled, the clip an animal is drawn in: those have no path a tool could have written down. A search walks the display list from where (the stage when it is left out), nearest first, and gives back places typed as the class you asked for, which read, write, call and hook like any other place.
The search runs inside one frame of the game (Finder.as), looking at up to 20,000 objects and answering at most 2,000, so narrow it with where, depth and limit rather than asking the stage for everything. through: 'properties' reads every member each class declares, getters included, which reaches what is never drawn at many times the cost. The places that come back are held by handle: $release the ones you are not keeping.
Parameters
| Name | Type | |
|---|---|---|
options optional | Options | undefined | Which objects to answer: ofClass, named, containing, visibleOnly, depth and limit. Every rule given has to hold. |
where optional | FlashPlaceLike | undefined | Where to start looking. The stage, which holds the whole game, when left out. |
Returns
Promise<FoundPlaces<Options["ofClass"]>>. A place for each object that matched, nearest the start first.
Types
Example
const buttons = await phantom.flash.find({
ofClass: 'flash.display.SimpleButton',
visibleOnly: true,
});
for (const button of buttons) console.log(await button.$at('name').$get());frames
frames(count?: number) => Promise<number>Waits for count frames of the game, and resolves with the frame it stopped on.
The game runs at about 24 frames a second, and slower when its window is hidden or the computer is busy. A wait in milliseconds is a guess at how many frames that was; this one is counted in the game, so a slow game is waited for rather than overtaken. It is the wait to use after asking the client to do something it finishes on a later frame: a window it lays out, a list it fills, a clip it moves to another frame of its timeline.
The frames are counted from the loader's own ticks (Ticks.as), which is a message a frame while the wait is on, shared with everything else listening every frame. A game whose page loads the client again mid-wait goes on counting the new client's frames rather than waiting for ever on a loader that is gone.
Parameters
| Name | Type | |
|---|---|---|
count optional | number | How many frames to wait. One by default, and never fewer. |
Returns
Promise<number>. The loader's number for the frame the wait ended on, which starts again from 1 each time the page loads the client.
Example
global
global<Name extends keyof ClientGlobals & string>(name: Name) => TypedFlash<ClientGlobals[Name]>
global(name: string) => AnyFlashA package-level variable or constant, by qualified name, such as gMainFrame.
Like use, but for what a package declares outside a class: the client's globals (gMainFrame, the game's MainFrame, is the one most things hang from) and package constants. The place is typed from the client's declarations, and nothing is sent until it is read, written or called.
Parameters
| Name | Type | |
|---|---|---|
name | Name | The variable's or constant's qualified name: gMainFrame, or some.pkg.NAME. |
Returns
TypedFlash<ClientGlobals[Name]>. The value's place, typed as the client declares it.
Types
Example
const main = phantom.flash.global('gMainFrame');
console.log(await main.userInfo.myUserName.$get());
console.log('in a minigame:', await main.isInMinigame());hide
hide(place: FlashPlaceLike) => Promise<Unsubscribe>Hides a display object until the function it gives back is called, even if the game shows it meanwhile.
The object is made invisible, and checked every frame before Flash draws, so the game setting it visible again never shows it. Calling the function gives the object the visibility it had. It is a highlight with hide set, so it counts toward the same limit.
It follows the place rather than the object: named by a path (use(...).member), it waits for an object that is not there yet, and moves onto the new one when the client builds it again, such as the HUD for the next room. Named by a handle, it stays on that one object.
Parameters
| Name | Type | |
|---|---|---|
place | FlashPlaceLike | The display object to hide: a typed place or a handle. |
Returns
Promise<Unsubscribe>. A function that shows the object again.
Types
Example
const hud = phantom.flash.use('gui.GuiManager').mainHud;
const show = await phantom.flash.hide(hud);
await sleep(10_000);
await show();highlight
highlight(place: FlashPlaceLike, options?: HighlightOptions) => Promise<Unsubscribe>Glows or recolours one of the game's display objects (an avatar's view, an NPC, a room object) until the function it gives back is called. The object's own filters stay as they are.
By default it is a purple glow. The game may replace an object's filters at any time (a rollover, a costume change), so every few frames the highlight is put back if it is gone. The object is held weakly: an avatar that leaves the room takes its highlight with it. A glow and a recolor matrix cannot be one highlight (it throws; make two), and the place must be a display object. At most 512 highlights are held.
Parameters
| Name | Type | |
|---|---|---|
place | FlashPlaceLike | The display object: a typed place, such as an avatar's world view, or a handle. |
options optional | HighlightOptions | The glow's colour, strength, blur, alpha, inner or knockout, a pulse, a recolor instead of the glow, or hide. |
Returns
Promise<Unsubscribe>. A function that takes the highlight off.
Types
FlashPlaceLike · HighlightOptions · Unsubscribe
Example
const view = phantom.flash.use('avatar.AvatarManager').playerAvatarWorldView;
const off = await phantom.flash.highlight(view, { color: 0xfacc15, strength: 3, pulse: true });
await sleep(5_000);
await off();hold
hold(place: FlashPlaceLike, value: unknown, options?: HoldOptions) => Promise<Unsubscribe>Keeps a value where the game keeps putting its own back, frame after frame.
Writing from here takes a message each way, so a member the client sets every frame (a speed, a scale, a flag it recomputes) is the client's again before the write lands. This one is kept in the game and written there, which is what holding a speed, a size or a flag takes. It lasts until the function it gives back is called, which puts back what was there first.
The value is plain data, another place in the game (read at every write), or a rule value such as phantom.flash.rules.fromClass(...). while narrows it to the frames a condition holds.
The path is read again on every write, so a hold waits while its object is not there yet (the HUD before it is drawn, a room between two rooms) and writes the new object when the client builds it again. Only a write the game refuses turns it off, which onFailed hears. Every field has the same as $keep: hud.alpha.$keep(0.5).
Parameters
| Name | Type | |
|---|---|---|
place | FlashPlaceLike | The member to write: place.member, not the object itself. |
value | unknown | What to write: plain data, a place to read it from, or a rule value. |
options optional | HoldOptions | everyFrames, while, once, restore and onFailed. |
Returns
Promise<Unsubscribe>. A function that stops holding it, and puts back what was there.
Types
FlashPlaceLike · Unsubscribe · HoldOptions
Example
const view = phantom.flash.use('avatar.AvatarManager').playerAvatarWorldView;
const big = await phantom.flash.hold(view.scaleX, 2);holdAll
holdAll(held: readonly Held[], options?: HoldOptions) => Promise<Unsubscribe>Keeps several values in place together, in the same frame as each other.
What hold does, for values that belong together: a position's x and y, a speed and the flag that lets it through. They are written in the order given, and one function ends them all.
Parameters
| Name | Type | |
|---|---|---|
held | readonly Held[] | The values to keep, each a place and a value. |
options optional | HoldOptions | everyFrames, while, once, restore and onFailed. |
Returns
Promise<Unsubscribe>. A function that stops holding them all.
Types
Held · Unsubscribe · HoldOptions
Example
const view = phantom.flash.use('avatar.AvatarManager').playerAvatarWorldView;
const stop = await phantom.flash.holdAll([
{ place: view.scaleX, value: 2 },
{ place: view.scaleY, value: 2 },
]);hook
hook(name: HookName, options?: HookOptions, listener?: ((call: HookCall) => void) | undefined) => Promise<HookStop>Hooks one of the client's methods, getters or setters: rules applied inside the call, and a listener told of calls afterwards. See HookOptions and phantom.flash.rules.
The name is checked against the client's hooks as you type: <class>.<member> for a method, with @get or @set for a getter or setter. Rules (skip, replaceArgs, replaceReturn, limited by when, first and every) act inside the call, so they can change what the client does; the listener hears about the call only afterwards, a message later, and cannot. With a listener, the call's start is heard with all its arguments by default; phase and capture ask for other things. At most perFrame calls a frame are told (20 by default), and rules that throw inside the game turn the hook off (onError hears it). It throws when there is no such hook, or a rule names an argument the method does not have.
Parameters
| Name | Type | |
|---|---|---|
name | HookName | The hook's name, such as avatar.AvatarManager.createAvatar. |
options optional | HookOptions | What to capture for the listener, and the rules to apply inside the call. |
listener optional | ((call: HookCall) => void) | undefined | Called after each call the hook tells of, with what was captured: the phase, the arguments by name and in order, paths on self, and the returned value at exit. |
Returns
Promise<HookStop>. The function that ends the hook, which also has update(options) for new rules on the same registration and stats() for its counts.
Types
HookOptions · HookCall · HookEntry · HookStop · HookStats · HookName
Example
const stop = await phantom.flash.hook('avatar.AvatarManager.createAvatar',
{ capture: { args: ['userName', 'x', 'y'] } },
(call) => console.log(call.args.userName, 'arrived'));hooks
hooks(url?: string | undefined) => Promise<HookIndex>The hooks Phantom's rewrite of the client put in, by name. Loaded once, from the address the game page gave the loader, or from url.
hook and override use this themselves, so a script needs it only to see what can be hooked: names() lists every hook, find(name) gives one's entry (its class, member, kind and parameters), and size counts them. The table is kept after the first load; giving url loads it again from there, and one that failed to load is asked for again next time.
Parameters
| Name | Type | |
|---|---|---|
url optional | string | undefined | Where to load the hook table from, instead of the address the game page gave. |
Returns
Promise<HookIndex>. The hook table, indexed by name.
Example
const index = await phantom.flash.hooks();
console.log(index.size, 'hooks');
console.log(index.names().filter((name) => name.startsWith('gui.GuiManager.')));inspect
inspect(place: FlashPlaceLike, options?: InspectOptions) => Promise<Inspection>A display object and everything under it, as plain data: each object's name, class, whether it is shown, its position, scale and size, its timeline (frame, label, labels), a text field's text, whether its parent's timeline placed it, and where it is drawn, in a few messages however big the tree is (one for the root and one per level).
It is the first thing to do before changing a piece of the client's screen: which child is the button, which of a button's states holds its picture, which clip is a timeline that will put the art back. outline() prints it, one object a line. Nothing is held after it answers, unless handles asks to keep each node's handle to act on (then release it).
It throws, saying where, when the place holds nothing or holds no display object.
Parameters
| Name | Type | |
|---|---|---|
place | FlashPlaceLike | The display object: a typed place, a handle, or a reference. |
options optional | InspectOptions | depth (4 levels by default), bounds (true for the root's space, 'stage', or a place), limit (500 objects by default) and handles. |
Returns
Promise<Inspection>. The tree, with outline, nodes, find and release.
Types
InspectOptions · Inspection · InspectedNode · InspectedBounds · InspectedFrames · FlashPlaceLike
Example
const card = phantom.flash.use('buddy.BuddyManager')._buddyCard._buddyCardMC;
const tree = await phantom.flash.inspect(card, { depth: 3, bounds: true });
console.log(tree.outline());
const ear = tree.find((node) => node.name === 'gameTabBtn');
console.log(ear?.bounds, ear?.children.map((child) => child.name));nextFrame
nextFrame() => Promise<number>Waits for the next frame of the game. The same as frames(1).
What the client changes in one frame it draws in the next, so this is the wait between writing something and reading what the client made of it.
Returns
Promise<number>. The loader's number for the frame.
Example
const me = phantom.flash.use('avatar.AvatarManager').playerAvatarWorldView;
await me.alpha.$set(0.5);
await phantom.flash.nextFrame();
const picture = await phantom.flash.snapshot(me);
console.log(picture.length, 'characters of PNG, drawn after the change');notes
notes<Value extends FlashData = FlashData>() => FlashNotes<Value>Notes of your own, kept beside the game's own objects.
The game's classes are sealed, so a tool cannot write what it knows onto a chest, a row or a player. A table of notes is where it writes instead: keyed by the object itself, so two that look alike are told apart, and held weakly, so a note never keeps the game's object alive. Make one table per thing you are remembering. The tool's tables end with the tool.
Returns
FlashNotes<Value>. An empty table: set, get, has, forget, objects and clear.
Types
Example
const greeted = phantom.flash.notes<number>();
const me = phantom.flash.use('avatar.AvatarManager').playerAvatar;
if (!(await greeted.has(me))) await greeted.set(me, Date.now());override
override<const Name extends HookName>(name: Name, handler: OverrideHandler<HookSignature<Name>>, ...[options]: OverrideOptionsArgument<HookSignature<Name>>) => Promise<OverrideStop>Replaces one of the client's methods with handler, which may run the original for the call (call.original(), with the same arguments or others) or not. The client carries on at once without waiting for the handler, so a method returning anything but void needs its value declared (returns). See OverrideOptions, and ./override.ts for how it runs in Flash and how it orders with other hooks on the same method.
The handler gets the call's self, its arguments by name (args), in order (argList) and one at a time (arg(name)), and original(). Objects in them are held by handle until the handler finishes. If the handler throws, the original runs (unless it already had, or onError: 'skip'). Calls are let through untouched past perFrame a frame (200, the most the game allows, by default) or with 256 waiting, and the first frame that lets any through is warned about unless onDropped is watching.
Note the order, which is the other way about from hook: the handler comes second and the options third. Getting it wrong throws, saying so.
Parameters
| Name | Type | |
|---|---|---|
name | Name | The hook's name, such as avatar.UserCommXtCommManager.sendChatMessage. |
handler | OverrideHandler<HookSignature<Name>> | Called with each call the override catches, after the client has moved on. It runs the client's own method with call.original(), or leaves it skipped by returning without. Handlers run one at a time in the order the calls came, unless sequential: false. |
__2 | OverrideOptionsArgument<HookSignature<Name>> | When to catch calls (when, first, every), arguments to replace first, and returns: what the skipped call returns to the client, required for any method that is not void. Also sequential, onError, perFrame and onDropped. |
Returns
Promise<OverrideStop>. A function that ends the override; calls caught before then still finish.
Types
OverrideHandler · HookSignature · OverrideCall · OverrideArgs · ReadArgs · ArgsOf · WriteArgs · OverrideOptionsArgument · OverrideOptions · ReturnsRequired · OverrideStop · HookName
Example
const stop = await phantom.flash.override('avatar.UserCommXtCommManager.sendChatMessage', async (call) => {
const [sender, text] = call.argList;
await call.original(sender, text.replace(/jam/g, 'JAM'));
});recolor
recolor(options: RecolorOptions, place?: FlashPlaceLike | undefined) => Promise<Unsubscribe>Changes the colours of the whole game, or of one display object, until the function it gives back is called: recolor('grayscale'), recolor({ hue: 90 }), recolor({ tint: 0xff0000, amount: 0.3 }).
Without a place, the colour change goes on the loaded game as a whole (Phantom's overlay, above it, keeps its colours). Looks: 'grayscale', 'sepia', 'invert' and 'night'; adjustments of brightness, contrast, saturation and hue; a tint over a share of the colour (amount, 0.5 by default); a colour matrix of twenty numbers; or a transform of multipliers and offsets. It is a highlight without a glow, kept and put back the same way.
It follows the place rather than the object: named by a path (use(...).member), it waits for an object that is not there yet, and moves onto the new one when the client builds it again, such as the HUD for the next room. Named by a handle, it stays on that one object.
Parameters
| Name | Type | |
|---|---|---|
options | RecolorOptions | The colour change: a named look, adjustments, a tint, a matrix or a transform. |
place optional | FlashPlaceLike | undefined | One display object to recolour. The whole game when left out. |
Returns
Promise<Unsubscribe>. A function that puts the colours back.
Types
RecolorOptions · FlashPlaceLike · Unsubscribe
Example
const day = await phantom.flash.recolor('night');
await sleep(30_000);
await day();
const view = phantom.flash.use('avatar.AvatarManager').playerAvatarWorldView;
const normal = await phantom.flash.recolor({ hue: 120, saturation: 1.5 }, view);repeat
repeat(method: FlashCallable, options?: RepeatOptions) => Promise<Unsubscribe>Has the game call one of its own methods, frame after frame, on its own.
Calling into the game from a script is a message each way, so a thing that has to happen every frame is late, and a call that throws while the game is not ready for it throws into your script. A repeat is the call itself, kept in the game: it happens in the frame, nothing crosses the bridge for it, and while decides in the game whether the frame is one to call on.
times calls it more than once in the same frame, which is how something the client does once a frame is made to happen twice.
Parameters
| Name | Type | |
|---|---|---|
method | FlashCallable | The method to call: place.method, not the object it belongs to. |
options optional | RepeatOptions | args, times, everyFrames, while, once and onFailed. |
Returns
Promise<Unsubscribe>. A function that stops the repeat.
Types
Example
const { exists, place } = phantom.flash.rules;
const room = phantom.flash.use('room.RoomManagerWorld').instance;
const quicker = await phantom.flash.repeat(room.movePlayerOnPath, {
while: exists(place(room, '_followPath')),
});search
search(query: string, options?: ClientSearchOptions) => Promise<ClientMatch[]>Finds a method, getter or setter anywhere in the client by part of its name.
The hook table read as an index, so a half-remembered name is enough: search('chatHist') answers with the class it is on and the name to pass hook and override. A query with a dot in it is read as class and member, so a name copied out of the client's own source searches as it is written. Nothing is sent to the game beyond loading the table once.
Only what can be hooked is in it, which is every method, getter and setter. A plain field has no hook, so ask describe about the class for those.
Parameters
| Name | Type | |
|---|---|---|
query | string | Part of a member's name, or Class.member. |
options optional | ClientSearchOptions | inClass, kind, isStatic and limit (50 by default). |
Returns
Promise<ClientMatch[]>. The matches, the closest first.
Example
for (const found of await phantom.flash.search('addMessage')) {
console.log(found.hook, found.parameters.map((each) => each.name).join(', '));
}snapshot
snapshot(place: FlashPlaceLike, options?: { scale?: number | undefined; background?: number | null | undefined; maxSize?: number | undefined; }) => Promise<string>One of the game's own display objects, drawn to a picture.
Flash has no picture encoder, so the loader writes the PNG itself and hands back a data: URL: a string you can show in a panel, save to a file, or send somewhere. What is drawn is the object's own content at its own size, with its filters and its children. The rotation and scale its parent gives it are left out, so an avatar comes back upright whatever the room is doing.
The picture is drawn and encoded inside one frame of the game, a pixel at a time (Snapshot.as), so its cost grows with its area: keep maxSize small for anything taken again and again, and do not take one every frame.
Parameters
| Name | Type | |
|---|---|---|
place | FlashPlaceLike | The display object to draw: a typed place, or a place read from the game. |
options optional | { scale?: number | undefined; background?: number | null | undefined; maxSize?: number | undefined; } | scale, how much bigger or smaller than its own size; background, a colour behind it, or nothing for a picture with nothing behind it; maxSize, the most pixels on a side, which the scale is cut down to fit. |
Returns
Promise<string>. The picture as a data:image/png;base64, URL.
Types
Example
const me = phantom.flash.use('avatar.AvatarManager').playerAvatarWorldView;
const picture = await phantom.flash.snapshot(me, { scale: 2, maxSize: 512 });
console.log(picture.startsWith('data:image/png'));stage
stage() => TypedFlash<Stage>Flash's stage, which the game is drawn on.
The root of everything shown: its size, frame rate, quality and focus, and every display object below it. For zoom, frame rate and quality there are ready-made calls in phantom.flash.view.
Returns
TypedFlash<Stage>. The stage as a typed place (flash.display.Stage).
Types
Example
const stage = phantom.flash.stage();
console.log(await stage.stageWidth.$get(), 'x', await stage.stageHeight.$get());
console.log('frames a second:', await stage.frameRate.$get());style
style(place: FlashPlaceLike, style: TextStyle) => Promise<Unsubscribe>Styles one of the client's text fields (font, size, colour, background, border, outline) until the function it gives back is called, which puts its look back. Text the client adds later is styled too.
The loader checks the field every few frames and styles it again if the client reset it. The place must be a TextField; anything else rejects. Leave out what should stay as it is.
It follows the place rather than the object: named by a path (use(...).member), it waits for a field that is not there yet, and moves onto the new field when the client builds it again, such as the HUD for the next room. Named by a handle, it stays on that one object.
Parameters
| Name | Type | |
|---|---|---|
place | FlashPlaceLike | The text field: a typed place, such as phantom.flash.use(...).member, or a handle. |
style | TextStyle | The look to give it: font, size, color, bold, italic, underline, align, leading, letterSpacing, background and border (a colour, or false for none), and outline, a glow around the letters. |
Returns
Promise<Unsubscribe>. A function that puts the field's own look back.
Types
TextStyle · FlashPlaceLike · Unsubscribe
Example
const plain = await phantom.flash.style(phantom.flash.use('gui.GuiManager').mainHud.$at('chatHistTxt'),
{ font: 'Verdana', size: 13, color: 0xf5f5f5, background: 0x111827 });track
track(className: string, listener: (change: { added: FlashPlace<unknown>[]; removed: FlashPlace<unknown>[]; }) => void, options?: { where?: FlashPlaceLike | undefined; mode?: TrackMode | undefined; everyFrames?: number | undefined; }) => Promise<Unsubscribe>Follows every instance of one of the game's classes, as they come and go.
A search tells you what is there now. This keeps a list current inside the game and tells you only what it gained and lost, so watching every avatar in a room, or every actor in an adventure, costs nothing on the frames when nothing changes. Anything that extends the class counts, and so does anything implementing it when you name an interface.
By default it walks what is drawn under where (the stage when it is left out). mode: 'holder' reads a collection the game keeps instead, which is where the things that are never drawn live. A look walks the whole tree under the place, so give a narrow where and an everyFrames that suits what is being followed rather than the stage every frame.
The places are held by handle, which is what tells one object from another between looks and what keeps the game from letting go of an object before you have heard that it left (Tracker.as remembers what it found weakly). So hold what arrived for as long as you care about it, and let go of it when you hear it leave, with $release on both.
A place is a new object each time it is heard of, so what tells two of them apart is the number the object is held by: place.$reference.target.handle.
Parameters
| Name | Type | |
|---|---|---|
className | string | The class or interface to follow, by qualified name. |
listener | (change: { added: FlashPlace<unknown>[]; removed: FlashPlace<unknown>[]; }) => void | Called with the places the list gained and lost, each time it changes. |
options optional | { where?: FlashPlaceLike | undefined; mode?: TrackMode | undefined; everyFrames?: number | undefined; } | where, the place to look under; mode, displayList or holder; everyFrames, how often the game looks. |
Returns
Promise<Unsubscribe>. A function that stops following.
Types
FlashPlaceLike · Unsubscribe · FlashPlace
Example
const stop = await phantom.flash.track('avatar.AvatarWorldView', ({ added, removed }) => {
const numberOf = (place: (typeof added)[number]) => {
const { target } = place.$reference;
return target.kind === 'handle' ? target.handle : 0;
};
for (const arrived of added) console.log('arrived', numberOf(arrived));
for (const left of removed) void left.$release();
});until
until<const Places extends readonly FlashPlaceLike[]>(places: Places, test?: ((values: { [Index in keyof Places]: WatchedValue<Places[Index]>; }) => boolean) | UntilOptions | undefined, options?: UntilOptions | undefined) => Promise<{ [Index in keyof Places]: WatchedValue<Places[Index]>; } | null>
until<Places extends Record<string, FlashPlaceLike>>(places: Places, test?: UntilOptions | ((values: { [Key in keyof Places]: WatchedValue<Places[Key]>; }) => boolean) | undefined, options?: UntilOptions | undefined) => Promise<{ [Key in keyof Places]: WatchedValue<Places[Key]>; } | null>
until<Place extends FlashPlaceLike>(place: Place, test?: UntilOptions | ((value: WatchedValue<Place>) => boolean) | undefined, options?: UntilOptions | undefined) => Promise<WatchedValue<Place> | null>Waits until a place in the game holds something, or until test says its value will do.
Nearly everything in the client is null until the client builds it: the HUD before play starts, a window before it opens, a list before the server answers. Reading it in a loop is a message each way for every look, and a fixed wait is a guess. This is read in the game and ends on the change itself, so the wait costs nothing while nothing happens.
Without test the whole wait is held in the game (the loader's when): one message out, one back when it holds or when the time is up, and the places' values come with it, read in the frame it held. test is TypeScript and has to be run here, so a wait with one is watched instead, which posts every change on the way to the one being waited for. So leave the test out where you can, and reach for phantom.flash.when where the test is a comparison the game could read itself.
Without test it waits for a value that is not null, undefined or false; a list or an object of places waits for every one of them. With test it waits for whatever you say, given the values in the shape you asked for them. An object the client holds there is read as { $object: className }, which is enough to know it is there; read the place afterwards for the object itself.
It answers null when the time runs out, rather than throwing, so check what came back.
Parameters
| Name | Type | |
|---|---|---|
places | Places | A place, a list of places, or an object of places by name. |
test optional | ((values: { [Index in keyof Places]: WatchedValue<Places[Index]>; }) => boolean) | UntilOptions | undefined | Whether the values will do. Left out, every value has to be something. The options may be given here instead, for a wait that only changes how long it is. |
options optional | UntilOptions | undefined | timeoutMs, ten seconds by default and Infinity for no limit, which is watched from here since the game holds a wait for ten minutes at the most; everyFrames, how often the game reads, every other frame by default; signal, to give the wait up, which answers null as running out of time does. |
Returns
Promise<{ [Index in keyof Places]: WatchedValue<Places[Index]>; } \| null>. The values that passed, in the shape of places, or null when the time ran out or the wait was given up on.
Types
UntilOptions · WatchedValue · FlashPlaceLike
Example
const gui = phantom.flash.use('gui.GuiManager');
if (await phantom.flash.until(gui.mainHud)) {
console.log('the HUD is up');
}
const view = phantom.flash.use('avatar.AvatarManager').playerAvatarWorldView;
const there = await phantom.flash.until(
{ x: view.x, y: view.y },
({ x, y }) => Number(x) > 400 && Number(y) > 300,
{ timeoutMs: 30_000 }
);
console.log(there ? 'arrived' : 'never got there');use
use<Name extends keyof ClientClasses & string>(name: Name) => TypedFlash<ClientClasses[Name]>
use(name: string) => AnyFlashA class, by qualified name: its static members, and $new to make one.
The name is the class's package and name, as in the decompiled client (avatar.AvatarManager, gui.GuiManager, flash.geom.Point). The place it gives is typed from the client's declarations, so its members complete and check in the editor; nothing is sent until a member is read ($get), written ($set), called, or listened to. Reading a static property that holds an object gives a place for it, held by handle. A name the client has no class for is only found out when the place is first used, which then rejects.
Parameters
| Name | Type | |
|---|---|---|
name | Name | The class's qualified name, such as avatar.AvatarManager. |
Returns
TypedFlash<ClientClasses[Name]>. The class as a typed place: its static members, and $new when it can be made.
Types
Example
const avatars = phantom.flash.use('avatar.AvatarManager');
console.log(await avatars.playerAvatar.userName.$get());
const point = await phantom.flash.use('flash.geom.Point').$new(10, 20);
console.log(await point.length.$get());watch
watch<const Places extends readonly FlashPlaceLike[]>(places: Places, options: WatchOptions, listener: (values: { [Index in keyof Places]: WatchedValue<Places[Index]>; }, previous: { [Index in keyof Places]: WatchedValue<Places[Index]>; } | null, errors: (string | null)[]) => void) => Promise<Unsubscribe>
watch<Places extends Record<string, FlashPlaceLike>>(places: Places, options: WatchOptions, listener: (values: { [Key in keyof Places]: WatchedValue<Places[Key]>; }, previous: { [Key in keyof Places]: WatchedValue<Places[Key]>; } | null, errors: Record<string, string | null>) => void) => Promise<Unsubscribe>
watch<Place extends FlashPlaceLike>(place: Place, options: WatchOptions, listener: (value: WatchedValue<Place>, previous: WatchedValue<Place> | null, error: string | null) => void) => Promise<Unsubscribe>Follows values in Flash: read there every few frames, and told here only when one changed. One place gives its value; a list, a list; an object of places, an object.
Every place is read in the same frame, so the values fit together, and the listener hears them only when at least one differs from the last reading. It is heard once at the start, with previous null. A place that cannot be read (an object that has gone) gives its error in errors rather than ending the watch. Watch few places, and not every frame, where it will do.
Parameters
| Name | Type | |
|---|---|---|
places | Places | A place, a list of places, or an object of places by name. |
options | WatchOptions | everyFrames: how often to read, 1 (every frame) by default; handles: give objects as typed places held by handle rather than { $object: className }. |
listener | (values: { [Index in keyof Places]: WatchedValue<Places[Index]>; }, previous: { [Index in keyof Places]: WatchedValue<Places[Index]>; } | null, errors: (string | null)[]) => void | Called when a value changed, with the values in the same shape as places, the ones before (null the first time), and each place's read error or null. |
Returns
Promise<Unsubscribe>. A function that stops watching.
Types
WatchOptions · FlashPlaceLike · Unsubscribe · WatchedValue
Example
const view = phantom.flash.use('avatar.AvatarManager').playerAvatarWorldView;
const stop = await phantom.flash.watch({ x: view.x, y: view.y }, { everyFrames: 6 }, ({ x, y }) =>
console.log(x, y));when
when(condition: HookWhen, options?: { read?: readonly FlashPlaceLike[] | undefined; everyFrames?: number | undefined; timeoutMs?: number | undefined; handles?: boolean | undefined; signal?: AbortSignal | undefined; }) => Promise<Waited>Waits for a condition inside the game, and reads the places you name in the frame it held.
until waits for a place to hold something, which is most waits. This is the whole of what the game can be asked to wait for: any condition phantom.flash.rules can build, read between the client's own calls. Nothing crosses the bridge while it waits, because the loop is in Flash, and read hands back the places' values from the frame the condition held, which is the only frame they are the values it held for.
Running out of time is an answer and not a throw: met is false and values is empty. Ten seconds by default, and ten minutes at the most, which is as long as the game will hold a wait.
rules.changed is what this op is really for: it holds when the value differs from what that comparison itself last read, so a wait fires on what is new rather than on every look. The first read counts as a change, as a watch's first reading does, and an object compares by being the same object, so the client building something again is a change and a field moving inside it is not.
Beware the near neighbour: whenever keeps something on an object the client makes again and again, where this waits once for a condition and answers.
Parameters
| Name | Type | |
|---|---|---|
condition | HookWhen | What to wait for, built with phantom.flash.rules. It is read between the client's calls, so it reads constants, a class's values and a place's, and rules.item has no meaning in it. |
options optional | { read?: readonly FlashPlaceLike[] | undefined; everyFrames?: number | undefined; timeoutMs?: number | undefined; handles?: boolean | undefined; signal?: AbortSignal | undefined; } | read, places read in the frame it held, at most 32; everyFrames, how often the game reads the condition, every frame by default; timeoutMs, how long to wait, ten seconds by default and ten minutes at the most; handles, to be handed the read values as places held by handle; signal, to give the wait up. |
Returns
Promise<Waited>. Whether it held, how many frames and milliseconds of the game's own clock it took, and what it read.
Types
HookWhen · HookTest · RawTest · Waited · FlashData · FlashPlace · FlashRead · FlashWrite · HoldOptions · FlashArgumentLike · FlashValue · FlashArgument · AnyFlash · FlashMethod · TypedFlash · FlashConstructor · FlashPlaceLike
Example 1
const hud = phantom.flash.use('gui.GuiManager').mainHud;
const waited = await phantom.flash.when(phantom.flash.rules.exists(hud), {
read: [hud],
timeoutMs: 30_000,
});
console.log(waited.met ? `the HUD came up after ${waited.frames} frames` : 'no HUD in half a minute');Example 2
const avatars = phantom.flash.use('avatar.AvatarManager');
const waited = await phantom.flash.when(phantom.flash.rules.changed(avatars.playerAvatar), {
timeoutMs: 120_000,
});
console.log(waited.met ? `switched after ${waited.frames} frames` : 'the same animal all along');whenever
whenever<Source extends WheneverSource>(place: Source, attach: (object: WheneverObject<Source>) => unknown, options?: WheneverOptions) => Promise<Unsubscribe>Runs attach on an object of the client's each time the client makes it, and takes off what it put on when the object goes.
The client throws away what it built and builds it again: the HUD for each room, a window each time it opens, an animal each time it is drawn. A listener, a label or a hidden part put on the old object is on an object nobody is looking at any more, which is why something that worked in one room does nothing in the next. This looks at the place every few frames, and at once on the moments the client is known to rebuild (a room and the HUD being built), and tells a new object from the old one by the object itself rather than by its class or its name.
attach is handed the object, held by handle, so everything it does lands on that object whatever the client does to the place meanwhile. What it returns is what takes it off again: it is called when the object is replaced, when it goes, and when the whenever is stopped. Nothing the client holds is kept alive by this beyond the one object it is on.
The place may also be a function that finds the object, for one that has no path: answer null while it is not there. It is run at every look, and an object it read to find the one it answers with is let go for it afterwards.
The looking is put back when the game's page loads the client again, since the tick and the hooks it looks on were asked of the loader that has gone (onReload). attach then runs afresh on the object the new client builds.
Parameters
| Name | Type | |
|---|---|---|
place | Source | The place to keep something on, or a function that finds it. |
attach | (object: WheneverObject<Source>) => unknown | Run on each new object there. A function it returns, or resolves with, is what takes its work off again; anything else it returns is left alone. |
options optional | WheneverOptions | everyFrames, how often to look; moments, the hooks to look at once on; onGone, told when the object has gone; onFailed, told what attach threw. |
Returns
Promise<Unsubscribe>. A function that stops looking and takes off what is on.
Types
WheneverObject · WheneverOptions · HookName · Unsubscribe
Example
const portrait = phantom.flash.use('gui.GuiManager').mainHud.$at('charWindow');
const stop = await phantom.flash.whenever(portrait, async (window) => {
await window.$at('buttonMode').$set(true);
return window.$listen('click', () => void phantom.aj.screens.open('avatarEditor'));
});whenReady
whenReady() => Promise<void>Resolves once the game's loader is running.
Calls made before then wait anyway, so this is for a script that wants to do something as soon as the game can be reached. It rejects when the game has closed.
Returns
Promise<void>
Example
await phantom.flash.whenReady();
console.log('stage quality', await phantom.flash.stage().quality.$get());Events
onKey
onKey(key: string | number, listener: (press: KeyPress) => void, options?: KeyOptions) => Promise<Unsubscribe>Hears a keyboard shortcut in the game: onKey('Ctrl+Shift+H', ...). The game hears the key as ever unless consume is set, which also keeps the character out of the focused text field and covers a held key's repeats; it is heard while the chat bar is empty but not while a message is being written, which whileTyping changes. Each press says whether the game was kept from it (consumed).
Keys are named as 'H', 'F2', 'Escape', 'Space', 'ArrowUp', 'Num5' and so on, with Ctrl, Shift and Alt joined by + (Cmd counts as Ctrl); a modifier not named must not be held. 'Any' hears every key, and a number is a Flash key code, heard whatever modifiers are held. A name it does not know throws. Keys are heard only while the game has the keyboard, and a held key's repeats only with repeat.
Parameters
| Name | Type | |
|---|---|---|
key | string | number | The shortcut, such as 'Ctrl+Shift+H', or a key code. |
listener | (press: KeyPress) => void | Called on each press with the key code, the character typed, the modifiers held and the phase. |
options optional | KeyOptions | phase ('down', the default, or 'up'), consume to keep the key from the game, whileTyping ('empty' by default, or 'never' or 'always') for what a focused text field does to it, and repeat for a held key's repeats. |
Returns
Promise<Unsubscribe>. A function that stops listening.
Types
KeyPress · KeyOptions · Unsubscribe
Example
let hidden: (() => Promise<void>) | null = null;
const stop = await phantom.flash.onKey('Ctrl+Shift+H', async () => {
if (hidden) { await hidden(); hidden = null; }
else hidden = await phantom.flash.hide(phantom.flash.use('gui.GuiManager').mainHud);
}, { consume: true });onReload
onReload(listener: () => void) => Promise<Unsubscribe>Hears the game's page load the client, which is the moment everything in Flash has to be put back.
Phantom loads the page again when the game switches server and when a dropped connection is picked up again, and the client reloading is the same thing from here: the loader's own bridge is built afresh (Bridge.as), holding none of the hooks, watches, drives, tracks, highlights, styles, keys or notes this side asked the one before it for. Nothing fails and nothing is said: the calls that ended them simply have nothing left to end, and a tool that was waiting on a hook waits for ever. A tool meant to run for hours starts its work in one function and runs that function again from here.
onTick, and the frames waits built on it, put themselves back without being asked. The first load of a game that is already up has happened before a tool is running, so the work is started once by hand and again on each reload.
Parameters
| Name | Type | |
|---|---|---|
listener | () => void | Called each time the page has loaded the game client again. |
Returns
Promise<Unsubscribe>. A function that stops listening.
Types
Example
let stop: (() => Promise<void>) | null = null;
const start = async () => {
stop = await phantom.flash.hook('room.RoomManagerWorld.sceneAssetsLoaded', {}, () =>
console.log('a room was built'));
};
await start();
await phantom.flash.onReload(() => void start());onTick
onTick(listener: (frame: number, time: number) => void, options?: TickOptions) => Promise<Unsubscribe>Hears every everyFramesth frame of the game (every frame by default), with its number and time.
For doing something in step with the game rather than on a timer: the game runs at about 24 frames a second, and slower when the window is hidden or the computer is busy. Each tick is a message out of the game's own frame (Ticks.as), so ask for every frame only when every frame is what the work needs. Listeners asking for the same rate share one tick in Flash, and one that throws is reported rather than left to silence the others beside it.
The ticks are asked for again whenever the game's page loads the client again, which Phantom does for a server switch and for a dropped connection. Everything else a script started in Flash goes with the loader that held it: hooks, watches, drives, tracks, highlights and keys all stop, with nothing said. onReload is how a long-running tool hears that and starts again.
Parameters
| Name | Type | |
|---|---|---|
listener | (frame: number, time: number) => void | Called on each tick with the loader's frame count and Flash's getTimer(), the milliseconds since Flash started. The count starts again at 1 each time the page loads the client. |
options optional | TickOptions | everyFrames: how many frames between ticks, 1 by default; onFailed: told what the listener threw, which is otherwise logged. |
Returns
Promise<Unsubscribe>. A function that stops the ticks.
Types
Example
const stop = await phantom.flash.onTick((frame, time) => {
if (frame % 240 === 0) console.log('frame', frame, 'at', time, 'ms');
}, { everyFrames: 24 });flash.overlay
Drawing above the game.
attach
attach(place: OverlayPlaceOptions, node: OverlayNode) => Promise<() => Promise<void>>Draws node inside one of the client's containers, above its children, so it moves, scales and hides with it; x and y are then in the container's coordinates. The container must let the mouse through to its children for the node to hear it. Resolves with what removes the node.
Use it for a node that belongs to something of the client's: a badge on a HUD button, a note on a popup, a label inside the map, where anchoring alone would draw it above everything. The node is taken out of any group or panel it was in, and a container given by path is looked up again now and then, so one the client rebuilds is found again.
Parameters
| Name | Type | |
|---|---|---|
place | OverlayPlaceOptions | The client's container to draw in: a typed place, a handle or a reference, or { place, refreshFrames } to say how often (in frames, 12 by default) a place given by path is looked up again. |
node | OverlayNode | A node drawn before, which moves into the container. |
Returns
Promise<() => Promise<void>>. What removes the node.
Example
const badge = await phantom.flash.overlay.text('3', {
x: 24, y: -4, font: { size: 11, bold: true }, background: 0xef4444, padding: 2,
});
const hud = phantom.flash.use('gui.GuiManager').mainHud;
const remove = await phantom.flash.overlay.attach(hud.$at('buddyListBtn'), badge);button
button(label: string, options?: ButtonOptions) => Promise<ButtonNode>Draws a button with a label. on('click') hears it; a disabled one keeps its clicks.
It is 72 by 24 pixels, purple with a bold white label, lighter under the mouse and darker while pressed, unless options say otherwise (fill, hoverFill, pressedFill, font). A press on it never reaches the game, so clicking it does not walk the player. A disabled button is faded, shows no hand cursor, and its clicks go no further: click listeners do not hear them. setLabel and setDisabled change it later.
Parameters
| Name | Type | |
|---|---|---|
label | string | The text on the button. |
options optional | ButtonOptions | Where it is (x, y, parent, anchor), its size, colours, font and disabled. |
Returns
Promise<ButtonNode>. The button: a node with setLabel and setDisabled.
Example
const dance = await phantom.flash.overlay.button('Dance', { x: 20, y: 440 });
const stop = dance.on('click', async () => {
await dance.setDisabled(true);
await phantom.aj.player.avatar.act('dance');
await dance.setDisabled(false);
});can
can(feature: string) => booleanWhether the loader in the game can do something, by the name it lists the feature under.
For a widget that wants a nicety the loader may not have: a draggable thumb on a scrollbar, a measurement to lay something out by. Asking first is the difference between a bar that cannot be dragged on an older loader and a scroll box that refuses to be drawn at all on one.
Parameters
| Name | Type | |
|---|---|---|
feature | string | The name, as the loader's own ready message lists them: gestures (the wheel, hovering and dragging), measure, widgets, align, batch and the rest. |
Returns
boolean. Whether this game's loader has it.
Example
if (phantom.flash.overlay.can('gestures')) console.log('the player can move things about');checkbox
checkbox(options?: CheckboxOptions) => Promise<CheckboxNode>A box the player ticks, with words beside it.
It is 14 pixels square with a green tick, and the words are clickable too. value() is whether it is ticked, without asking Flash; setValue sets it without telling anybody; onChange hears the player change it. It is a group of nodes rather than a widget of the loader's, so removing it removes the lot and everything the overlay does to a node works on it.
Parameters
| Name | Type | |
|---|---|---|
options optional | CheckboxOptions | Its label, whether it is checked, the boxSize, its colours (fill, stroke, tickFill), disabled, and where it is drawn as any node is. |
Returns
Promise<CheckboxNode>. The checkbox: a node with value, setValue, setDisabled and onChange.
Example
const quiet = await phantom.flash.overlay.checkbox({
x: 20, y: 20, label: 'Hide trade requests', checked: true,
});
quiet.onChange((ticked) => console.log('hiding:', ticked));
console.log('ticked now:', quiet.value());circle
circle(options: OverlayOptions) => Promise<OverlayNode>Draws a circle around its position.
A circle is always centred on its position, so centered changes nothing. Give it a radius and a fill or a stroke; with neither it is drawn as nothing. For a circle at a place in the room rather than on the screen, marker is shorter.
Parameters
| Name | Type | |
|---|---|---|
options | OverlayOptions | Its radius, fill and stroke, and where its centre is (x, y, anchor, parent). |
Returns
Promise<OverlayNode>. The node; calling it removes the circle.
Example
const dot = await phantom.flash.overlay.circle({
x: 480, y: 300, radius: 10, fill: { color: 0xef4444, alpha: 0.7 }, stroke: 0xffffff,
});
dot.on('over', () => void dot.update({ radius: 14 }));
dot.on('out', () => void dot.update({ radius: 10 }));clear
clear() => Promise<void>Removes everything this overlay drew.
Every node drawn through this phantom.flash.overlay goes, with what was drawn in it, and the client's objects that nodes replaced show again. What other scripts and plugins drew stays. What a script drew is removed anyway when its bridge closes; this is for starting over while it runs.
Returns
Promise<void>
Example
await phantom.flash.overlay.text('Loading...', { x: 20, y: 20 });
await sleep(2000);
await phantom.flash.overlay.clear();column
column(nodes: readonly OverlayNode[], options?: LayoutOptions) => Promise<LayoutSize>Lays nodes out in a line down the screen, one under the next, and says what the line takes up.
The same as row, down rather than across: each node is moved so its top follows the bottom of the one before it, with gap pixels between, and align decides how the narrower ones sit.
Parameters
| Name | Type | |
|---|---|---|
nodes | readonly OverlayNode[] | The nodes to place, in the order they go; each must still be drawn. |
options optional | LayoutOptions | Where the line starts (x, y), the gap between nodes, and how the narrower ones sit across it (align). |
Returns
Promise<LayoutSize>. The width and height of everything placed.
Example
const panel = await phantom.flash.overlay.panel({ title: 'Buddies', width: 200, height: 120 });
const rows = [];
for (const name of ['sxip', 'jammer123', 'greely']) {
rows.push(await phantom.flash.overlay.text(name, { parent: panel }));
}
const size = await phantom.flash.overlay.column(rows, { x: 8, y: 8, gap: 2 });
await panel.update({ height: size.height + 38 });draw
draw(type: "group" | "text" | "rect" | "circle" | "line" | "image" | "input" | "button" | "panel" | "list", options?: OverlayOptions) => Promise<OverlayNode>Draws a node of any type, by name. The shape helpers (text, rect, circle, line, image, group) and the controls (input, button, panel, list) all come down to this; use it when the type is chosen at run time. It gives a plain OverlayNode: a control drawn here has none of its helper's defaults (sizes, colours) or extra methods (value, setLabel, append), so draw controls with their own helpers.
The node is drawn in the loader's overlay sprite above the game, which stays on top of the stage every frame. It rejects if the loader refuses the spec: an unknown type, more than 2000 nodes at once, or a node given more than one of parent, host and replaces.
Parameters
| Name | Type | |
|---|---|---|
type | "group" | "text" | "rect" | "circle" | "line" | "image" | "input" | "button" | "panel" | "list" | The kind of node: group, rect, circle, line, text, image, input, button, panel or list. |
options optional | OverlayOptions | Where and how it is drawn: position (stage pixels, or its parent's coordinates), anchor, colours, size, text and the rest of OverlayOptions. Colours may be plain numbers. |
Returns
Promise<OverlayNode>. The node: call it (or its remove) to remove it, update to change it, on to hear the mouse on it.
Example
const kind = Math.random() < 0.5 ? 'circle' : 'rect';
const dot = await phantom.flash.overlay.draw(kind, {
x: 400, y: 250, radius: 6, width: 12, height: 12, fill: 0xef4444,
});
await sleep(3000);
await dot();focusGame
focusGame() => Promise<void>Gives the keyboard back to the game, from an input that had it.
It clears the stage's focus (stage.focus = null), so keys go to the game again: the arrow keys walk, and the chat bar hears typing once clicked. Call it after an input is submitted, so the player need not click the room first.
Returns
Promise<void>
Example
const search = await phantom.flash.overlay.input({ x: 20, y: 60, placeholder: 'Find a player' });
search.on('submit', async (name) => {
console.log('looking for', name);
await phantom.flash.overlay.focusGame();
});group
group(options?: OverlayOptions) => Promise<OverlayNode>Draws an empty group, to hold other nodes and move, hide or remove them together.
Nodes drawn with the group as their parent are placed from the group's own position, so moving the group (update({ x, y })) moves them all, hide hides them all, and removing the group removes everything drawn in it. A group may be anchored like any node, which is how a label, a bar and an icon follow an avatar as one.
Parameters
| Name | Type | |
|---|---|---|
options optional | OverlayOptions | Where the group is (x, y, anchor, parent), and its alpha, scale, rotation and visible, which apply to everything in it. |
Returns
Promise<OverlayNode>. The group; pass it as other nodes' parent, and call it to remove it with them.
Example
const box = await phantom.flash.overlay.group({ x: 20, y: 120 });
await phantom.flash.overlay.rect({ parent: box, width: 160, height: 40, fill: 0x111827, cornerRadius: 6 });
await phantom.flash.overlay.text('Phantom', { parent: box, x: 10, y: 10 });
await box.update({ x: 60 });image
image(url: string, options?: OverlayOptions) => Promise<OverlayNode>Draws an image from the content origin, or a base64 data: URL.
The loader loads it as an image only: a SWF given here may not run its code. With both width and height it is drawn at that size; with one, the other keeps its shape; with neither, its own size. centered centres it on its position. It is drawn once it loads, and the node exists before then; an image that does not load (a bad URL, a blocked origin, a data: URL that is not base64) is reported to onFailed, not by the promise. One that loads but comes from a domain without a permissive crossdomain.xml cannot be measured or resized either: it is drawn at its own size instead of width/height, and that too is reported to onFailed.
Parameters
| Name | Type | |
|---|---|---|
url | string | The image's address: a URL the game can load, or data:image/png;base64,.... |
options optional | OverlayOptions | Where and how big: x, y or anchor, width, height, centered, alpha. |
Returns
Promise<OverlayNode>. The node; calling it removes the image.
Example
const icon = await phantom.flash.overlay.image('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', {
x: 20, y: 20, width: 32, height: 32,
});
const stopHearing = icon.onFailed((message) => console.warn(message));input
input(options?: InputOptions) => Promise<InputNode>A text input. Keys typed in it never reach the game (its walking, its popups, its chat bar); Phantom's own shortcuts stay quiet while it has focus unless they hear typing.
It is 200 by 24 pixels, white with a grey border and dark text unless options say otherwise. on('change') hears each edit and on('submit') hears Enter (Ctrl+Enter when multiline), both with the text; on('focus') and on('blur') hear it take and lose the keyboard. value() gives the text as last heard or set, without asking Flash; setValue sets it without a change; focus gives it the keyboard; phantom.flash.overlay.focusGame() gives the keyboard back. disabled makes it read-only and faded.
Parameters
| Name | Type | |
|---|---|---|
options optional | InputOptions | Its value, placeholder, maxLength, restrict, multiline and disabled, and where and how it is drawn (x, y, parent, width, height, fill, font...). |
Returns
Promise<InputNode>. The input: a node with value, setValue and focus.
Example
const say = await phantom.flash.overlay.input({ x: 20, y: 400, width: 240, placeholder: 'Say to room', maxLength: 60 });
const stop = say.on('submit', async (text) => {
await phantom.aj.chat.say(text);
await say.setValue('');
});
await say.focus();inspect
inspect(node: OverlayNode) => Promise<OverlayInspection>A node and everything drawn in it: where each was put, the size it was drawn with and where Flash has it now. Each node's bounds are in the coordinates of what it is drawn in, as bounds() gives them. Needs a loader that can measure (measure).
Parameters
| Name | Type | |
|---|---|---|
node | OverlayNode | A node still drawn. |
Returns
Promise<OverlayInspection>. The node, its children inside it, and theirs.
Example
const panel = await phantom.flash.overlay.panel({ title: 'Notes' });
await phantom.flash.overlay.text('hello', { parent: panel, x: 4, y: 4 });
console.log(JSON.stringify(await phantom.flash.overlay.inspect(panel), null, 2));line
line(options: OverlayOptions) => Promise<OverlayNode>Draws a straight line from the node's position to toX, toY.
toX and toY are measured from the node's own position, not the stage's corner, so an anchored line keeps its shape as it follows its anchor. The line is 1 pixel and white unless stroke says otherwise; fill is not used.
Parameters
| Name | Type | |
|---|---|---|
options | OverlayOptions | Where it starts (x, y, anchor, parent), where it ends (toX, toY, from the start), and its stroke. |
Returns
Promise<OverlayNode>. The node; calling it removes the line.
Example
const view = phantom.flash.use('avatar.AvatarManager').playerAvatarWorldView;
const pointer = await phantom.flash.overlay.line({
anchor: view, toX: 120, toY: -60, stroke: { color: 0xfacc15, width: 3 },
});list
list(options?: ListOptions) => Promise<ListNode>Draws rows of rich text in a scrolling box; rows and the links in them are clickable.
It is 240 by 160 pixels, half-transparent black with white text, unless options say otherwise. A row is plain text, spans, or { markup }; long rows wrap. append adds rows at the end without sending the rest again, and the oldest go once there are more than maxRows (200 by default, 1000 at most). While scrolled to the end it follows new rows, unless stickToEnd is false. on('row') hears a row clicked, by its index among the rows kept now, and on('link') a link in one clicked. It is the piece to build a chat log of your own on.
Parameters
| Name | Type | |
|---|---|---|
options optional | ListOptions | Its rows to begin with, maxRows, stickToEnd, size, colours and font, and where it is (x, y, parent). |
Returns
Promise<ListNode>. The list: a node with append and setRows.
Example
const log = await phantom.flash.overlay.list({ x: 20, y: 120, width: 280, height: 140, maxRows: 100 });
const stop = phantom.aj.chat.onMessage((message) =>
void log.append([{ text: `${message.userId}: `, bold: true, color: 0xfacc15 }, { text: message.text }]));
log.on('row', (index) => console.log('clicked row', index));marker
marker(point: Point, options?: OverlayOptions & { kind?: "group" | "text" | "rect" | "circle" | "image" | undefined; }) => Promise<OverlayNode>Something drawn at a place in the room rather than on the screen: a marker over a chest, an arrow at a doorway, a label on a spot. It follows that place, and hides itself while the room it belongs to is not the one on screen.
It is a node drawn with anchor: world(point): a circle of radius 8 unless kind or radius says otherwise, with options as any node takes them (fill, stroke, text, url).
Parameters
| Name | Type | |
|---|---|---|
point | Point | Where in the room, in room coordinates (as phantom.aj.player.avatar.position() gives them). |
options optional | OverlayOptions & { kind?: "group" | "text" | "rect" | "circle" | "image" | undefined; } | How it is drawn, and kind: circle (the default), rect, text, image or group. An anchor given here is ignored; the marker's is the point. |
Returns
Promise<OverlayNode>. The node; calling it removes the marker.
Example
const { x, y } = (await phantom.aj.player.avatar.position()) ?? { x: 0, y: 0 };
const spot = await phantom.flash.overlay.marker({ x, y: y - 40 }, {
fill: { color: 0xfacc15, alpha: 0.8 }, stroke: 0x000000,
});
spot.on('click', () => console.log('marker clicked'));measure
measure(text: string | TextSpan[], options?: { font?: Partial<OverlayFont> | undefined; padding?: number | undefined; wrapWidth?: number | undefined; }) => Promise<OverlayMetrics>How big text would be drawn, without drawing it.
Flash measures it the way it would draw it, so a label can be sized, centred or wrapped before anything is on screen, and a panel can be made to fit its contents rather than guessed at. The numbers include Flash's own two pixel gutter at each edge of a text field, which is what a text node takes up, and padding is added the way that node's background box adds it.
Parameters
| Name | Type | |
|---|---|---|
text | string | TextSpan[] | The words to measure, or spans of rich text (parseMarkup turns markup into spans). |
options optional | { font?: Partial<OverlayFont> | undefined; padding?: number | undefined; wrapWidth?: number | undefined; } | Its font and padding, as the node would have them, and wrapWidth to wrap it at over as many lines as it takes. |
Returns
Promise<OverlayMetrics>. Its width and height in pixels, and how many lines it is drawn on.
Example
const font = { size: 14, bold: true };
const size = await phantom.flash.overlay.measure('Gems: 1200', { font, padding: 4 });
const box = await phantom.flash.overlay.rect({
x: 20, y: 20, width: size.width, height: size.height, fill: { color: 0x000000, alpha: 0.6 },
});
await phantom.flash.overlay.text('Gems: 1200', { parent: box, x: 4, y: 4, font });panel
panel(options?: PanelOptions) => Promise<PanelNode>A box with a title bar that holds other nodes (drawn with it as their parent, placed from its top left corner below the title bar, and clipped to it). Dragged by its title bar, sized from its corner when resizable. Where it ends up is the caller's to keep, from onMove and onResize.
It is 260 by 200 pixels, dark and nearly opaque, with a 22 pixel title bar, unless options say otherwise. The loader moves and sizes it itself as it is dragged, so nothing crosses the bridge until the drag ends; onMove is then told its new position (in its parent's coordinates) and onResize its new size. The panel does not remember them between runs: keep them and pass them back as x, y, width and height. A press on the panel never reaches the game.
Parameters
| Name | Type | |
|---|---|---|
options optional | PanelOptions | Its title, size, colours (fill, titleFill, titleFont), draggable, resizable, minWidth and minHeight, where it is, and onMove and onResize to hear it dragged. |
Returns
Promise<PanelNode>. The panel: pass it as other nodes' parent; its on('move') and on('resize') hear it dragged.
Example
const panel = await phantom.flash.overlay.panel({ title: 'Buddies', x: 600, y: 60, width: 200, height: 160 });
const list = await phantom.flash.overlay.list({ parent: panel, width: 200, height: 138 });
await list.setRows(['[b]sxip[/b] online', 'jammer123 offline'].map((markup) => ({ markup })));
const stop = panel.on('move', ({ x, y }) => console.log('panel now at', x, y));place
place(nodes: readonly OverlayNode[], points: readonly (Point | null | undefined)[]) => Promise<LayoutSize>Puts each node where the matching point says, measuring it first so that its box lands there.
row and column are lines; this is the same placing without a shape, for a layout that works out its own positions. That is the half a script cannot do itself: a node's own position is not where its ink starts, since a centred label is drawn about its point and a group's children may reach left of it, and a node will not say where it was last put. So each node is measured where it stands and moved by the difference between where its box is and where the point wants it.
It is what phantom.ui.box lays a grid of cards out with, and it is here rather than there because the correction needs the position the overlay last drew the node at.
Parameters
| Name | Type | |
|---|---|---|
nodes | readonly OverlayNode[] | The nodes to put somewhere; each must still be drawn. |
points | readonly (Point | null | undefined)[] | Where each one's box goes, in the coordinates it is drawn in, one a node. A node whose point is left out, or given as nothing, stays where it is. |
Returns
Promise<LayoutSize>. How far right and how far down the placed boxes reach, from the origin they are placed in.
Example
const left = await phantom.flash.overlay.text('Mods', { centered: true });
const right = await phantom.flash.overlay.text('Enabled', { centered: true });
const size = await phantom.flash.overlay.place([left, right], [{ x: 10, y: 10 }, { x: 10, y: 30 }]);
console.log('they fill', size.width, 'by', size.height);rect
rect(options: OverlayOptions) => Promise<OverlayNode>Draws a rectangle, filled, outlined or both.
It is drawn from its position to the right and down, or around it when centered, and cornerRadius rounds it. With neither fill nor stroke it is drawn as nothing; with no width or height it has no size. Like any shape it lets the mouse through to the game until something listens with on.
Parameters
| Name | Type | |
|---|---|---|
options | OverlayOptions | Its width, height, fill, stroke, cornerRadius and centered, and where it is (x, y, anchor, parent). |
Returns
Promise<OverlayNode>. The node; calling it removes the rectangle.
Example
const view = phantom.flash.use('avatar.AvatarManager').playerAvatarWorldView;
const box = await phantom.flash.overlay.rect({
anchor: { place: view, y: -40 }, centered: true, width: 70, height: 90,
stroke: { color: 0x22c55e, width: 2 },
});replace
replace(place: OverlayPlaceOptions, node: OverlayNode) => Promise<() => Promise<void>>Hides one of the client's display objects and draws node in its place: in the same parent, just above it, at its position and (unless the node has its own) its size. The object stays hidden while the node lasts, even when the client shows it again, and a place given by path is looked up again now and then, so a HUD the client rebuilds is replaced too. Resolves with what removes the node, which shows the object again.
The node is taken out of any group, panel or container it was in first. The node's x and y then offset it from the object's position. Only one of parent, host and replaces may be set on a node at once.
Parameters
| Name | Type | |
|---|---|---|
place | OverlayPlaceOptions | The client's display object to stand in for: a typed place, a handle or a reference, or { place, refreshFrames } to say how often (in frames, 12 by default) a place given by path is looked up again. |
node | OverlayNode | A node drawn before, which moves to where the object is. |
Returns
Promise<() => Promise<void>>. What removes the node and shows the object again.
Example
const hud = phantom.flash.use('gui.GuiManager').mainHud;
const box = await phantom.flash.overlay.input({ placeholder: 'Say something' });
const restore = await phantom.flash.overlay.replace(hud.$at('chatBar'), box);rich
rich(content: string | TextSpan[], options?: OverlayOptions) => Promise<OverlayNode>Draws rich text: spans, or markup such as [b][color=#ff9900]sxip[/color][/b]: hi (parseMarkup).
Each span may have its own colour, weight and link; font sets what a span does not. Text with a link takes the mouse (a click on it does not walk the player there) and a click on a link is heard with on('link'), with the link's value. Without links it lets the mouse through, as text does. Change it later with update({ rich }).
Parameters
| Name | Type | |
|---|---|---|
content | string | TextSpan[] | Spans (TextSpan[]), or a markup string read by parseMarkup. |
options optional | OverlayOptions | Where and how, as for text: position or anchor, the default font, background, padding, outline and centered. |
Returns
Promise<OverlayNode>. The node; calling it removes the text.
Example
const line = await phantom.flash.overlay.rich('[b][color=#22c55e]Online[/color][/b] as sxip', {
x: 12, y: 40, background: { color: 0x000000, alpha: 0.5 }, padding: 4,
});
await line.update({ rich: '[b][color=#ef4444]Offline[/color][/b]' });row
row(nodes: readonly OverlayNode[], options?: LayoutOptions) => Promise<LayoutSize>Lays nodes out in a line across, one after the next, and says what the line takes up.
Each node is measured where it is and moved so that it follows the one before it, with gap pixels between. That is the arithmetic a script would otherwise do by hand, with numbers it cannot know: how wide a label came out, how tall a wrapped one is. The nodes keep whatever they are drawn in, so lay out a panel's contents by passing nodes whose parent is that panel.
Parameters
| Name | Type | |
|---|---|---|
nodes | readonly OverlayNode[] | The nodes to place, in the order they go; each must still be drawn. |
options optional | LayoutOptions | Where the line starts (x, y), the gap between nodes, and how the shorter ones sit across it (align). |
Returns
Promise<LayoutSize>. The width and height of everything placed.
Example
const panel = await phantom.flash.overlay.panel({ title: 'Trade', width: 240, height: 90 });
const label = await phantom.flash.overlay.text('Sending:', { parent: panel });
const gems = await phantom.flash.overlay.text('1200 gems', { parent: panel, font: { bold: true } });
const size = await phantom.flash.overlay.row([label, gems], { x: 8, y: 8, gap: 4 });
console.log('the line is', size.width, 'wide');scroller
scroller(options?: ScrollerOptions) => Promise<ScrollerNode>A box that scrolls whatever is drawn in it, under the wheel.
list scrolls rows of rich text; this scrolls nodes: pictures, shapes, controls, anything. Draw them with content as their parent, and call refresh() after drawing or removing one so it knows how far there is to go. The window is a panel with no title bar, which is the one node that clips what is drawn in it.
Parameters
| Name | Type | |
|---|---|---|
options optional | ScrollerOptions | Its width and height, how far one turn of the wheel moves it (step), its fill, and where it is drawn. |
Returns
Promise<ScrollerNode>. The box: a node with content, top, scrollTo, scrollBy, refresh and onScroll.
Example
const box = await phantom.flash.overlay.scroller({ x: 20, y: 80, width: 200, height: 120 });
for (let index = 0; index < 20; index++) {
await phantom.flash.overlay.text(`line ${index}`, { parent: box.content, x: 4, y: index * 16 });
}
await box.refresh();
box.onScroll((top) => console.log('scrolled to', top));select
select(options?: SelectOptions) => Promise<SelectNode>A dropdown: a button showing what is chosen, which opens a list of the rest.
The choices are plain strings, or { value, label } where what you keep and what the player reads differ. Picking one closes the list and tells onChange; opens: 'up' is for a control near the bottom of the screen. The list is drawn inside the control, so a select in a panel needs the room below it inside that panel, since a panel clips what is drawn in it.
Parameters
| Name | Type | |
|---|---|---|
options optional | SelectOptions | Its choices, the value chosen to begin with, the placeholder, rowHeight, maxRows, which way it opens, disabled, and its size and place as any node's. |
Returns
Promise<SelectNode>. The dropdown: a node with value, setValue, setChoices, open, close and onChange.
Example
const room = await phantom.flash.overlay.select({
x: 20, y: 60, width: 180, placeholder: 'Go to...',
choices: [{ value: 'jamaa.room.township', label: 'Jamaa Township' }, 'sarepia.room.main'],
});
room.onChange((value) => void phantom.aj.room.join(value));slider
slider(options?: SliderOptions) => Promise<SliderNode>A slider: a track, and a knob the player drags along it.
The knob is dragged in Flash, so nothing crosses the bridge while it moves, and onChange is told the value as it goes. step makes it hold whole values, and the knob lands on them.
Parameters
| Name | Type | |
|---|---|---|
options optional | SliderOptions | Its min, max, value, step, the track's length, the knobRadius, their colours (trackFill, knobFill), and where it is drawn. |
Returns
Promise<SliderNode>. The slider: a node with value, setValue and onChange.
Example
const zoom = await phantom.flash.overlay.slider({
x: 20, y: 120, min: 50, max: 200, value: 100, step: 5, length: 140,
});
zoom.onChange((percent) => void phantom.aj.room.camera.zoom(percent));tabs
tabs(options: TabsOptions) => Promise<TabsNode>A row of tabs, each with a group of its own that is shown while its tab is.
Draw a tab's contents with page(name) as their parent and they are hidden and shown with it. Each tab is as wide as its label unless tabWidth says otherwise, which is measure doing the arithmetic. show changes tab from code, as clicking would.
Parameters
| Name | Type | |
|---|---|---|
options | TabsOptions | Its tabs, which is active first, tabWidth, tabHeight, the gap between them, their colours (activeFill, idleFill), and where the row is drawn. |
Returns
Promise<TabsNode>. The tabs: a node with page, show, value and onChange.
Example
const panel = await phantom.flash.overlay.panel({ title: 'Shop', width: 260, height: 200 });
const tabs = await phantom.flash.overlay.tabs({
parent: panel, x: 8, y: 8, tabs: [{ name: 'buying' }, { name: 'selling' }],
});
await phantom.flash.overlay.text('nothing yet', { parent: tabs.page('buying'), x: 4, y: 4 });
tabs.onChange((name) => console.log('showing', name));text
text(text: string, options?: OverlayOptions) => Promise<OverlayNode>Draws plain text, in one font, at a point on the stage or following an anchor.
The font is _sans, 12 pixels and white unless font says otherwise. background puts a rounded box behind it, padding pads that box, outline draws a glow round the letters in that colour (for text that must read over any room), and centered centres it on its position. Change the text later with update({ text }). Plain text lets the mouse through to the game unless something listens with on. For several colours or links, use rich.
Parameters
| Name | Type | |
|---|---|---|
text | string | The text to show. |
options optional | OverlayOptions | Where and how: x, y or anchor, font, background, padding, outline, centered and the rest of OverlayOptions. |
Returns
Promise<OverlayNode>. The node; calling it removes the text.
Example
const name = (await phantom.aj.player.current())?.userName;
const label = await phantom.flash.overlay.text(`Playing as ${name}`, {
x: 12, y: 12, font: { size: 14, bold: true }, background: { color: 0x000000, alpha: 0.5 }, padding: 4,
});
await label.update({ text: 'Still here' });world
world(point?: Point, options?: { refreshFrames?: number | undefined; }) => OverlayAnchorOptionsThe layer the room is drawn in, as a place to anchor to: the player's avatar's own parent, whose coordinates are the ones phantom.aj.player.avatar.position() and an adventure's actor positions are in.
Anchoring to it with a point (world({ x, y })) puts a node at that place in the room, and the loader keeps it there as the camera scrolls, the room zooms and the player walks about.
It draws nothing and asks nothing of Flash: it only builds the anchor, for a node's anchor option. The layer is found by path (avatar.AvatarManager.playerAvatarWorldView.parent), so it is looked up again every refreshFrames frames, and a node anchored to it is hidden while there is no player avatar on the stage (while logging in, or between rooms).
Parameters
| Name | Type | |
|---|---|---|
point optional | Point | The place in the room, in room coordinates: the room's top left corner by default. |
options optional | { refreshFrames?: number | undefined; } | refreshFrames: how often the room layer is looked up again, in frames (12 by default). |
Returns
OverlayAnchorOptions. An anchor to pass as a node's anchor.
Example
const { x, y } = (await phantom.aj.player.avatar.position()) ?? { x: 0, y: 0 };
const flag = await phantom.flash.overlay.text('I was here', {
anchor: phantom.flash.overlay.world({ x, y }),
centered: true,
background: { color: 0x000000, alpha: 0.6 },
});flash.ui
Rules over the game's own screen: hide, restyle or change every object of a class.
rule
rule<const Name extends string>(target: UiTarget<Name>, apply: (element: UiElement<Declared<Name>>) => unknown, options?: UiRuleOptions<Declared<Name>>) => Promise<UiRule>Applies apply to every object of a class the game draws, now and as more appear, and resolves with the running rule. target is the class by name, or { ofClass, where, mode } to look only under one place. apply is handed each object as a UiElement: what it changes through the element is undone when the rule stops or the object goes, and a function it returns is called then too. Call the rule to stop it; refresh() asks unless again for every object.
Parameters
| Name | Type | |
|---|---|---|
target | UiTarget<Name> | |
apply | (element: UiElement<Declared<Name>>) => unknown | |
options optional | UiRuleOptions<Declared<Name>> |
Returns
Promise<UiRule>
Example
const dim = await phantom.flash.ui.rule('avatar.AvatarWorldView', (el) => el.keep('alpha', 0.4));
console.log(dim.count, 'avatars dimmed');flash.bridge
The untyped bridge underneath, for anything the declarations do not describe.
client
client: string | nullThe client the loader loaded, once it has said.
It is what the loader's ready message named, and null until that message arrives. It names the client build the loader was pointed at, not whether that client finished loading: onClient hears that.
Example
await phantom.flash.bridge.whenReady();
console.log('Playing on client', phantom.flash.bridge.client);features
features: readonly string[]What the loader said it can do beyond version 1 (BRIDGE_FEATURES).
Read from the loader's ready message, so it is empty until the bridge is ready. Requests are batched only when it lists batch, and close can end what the bridge started in Flash only when it lists dispose. Check it before using a request an older loader may not know, such as override, widgets or style.
Example
await phantom.flash.bridge.whenReady();
if (!phantom.flash.bridge.features.includes('style')) {
console.warn('This loader cannot style text fields');
}reloads
reloads: numberHow many times the loader has started over under this bridge: 0 until the page has been loaded again.
Phantom loads the game's page on a server switch and on a reconnect, and the loader that comes back holds none of the numbers the one before it handed out. The bridge asks it for everything again by itself, so this is for telling, not for doing: a count that has gone up means the handles this bridge handed out are void, since an object in the loader that has gone cannot be reached from the one running now.
Example
const was = phantom.flash.bridge.reloads;
await phantom.flash.onReload(() => console.log('reloads so far:', phantom.flash.bridge.reloads));
console.log('reloads before we started:', was);state
state: BridgeStateWhere the bridge is: waiting for the loader to say it runs, ready, or closed for good.
A bridge starts waiting and becomes ready when the loader's ready message arrives (a loader speaking another bridge version closes it instead). Once closed, every request fails with CLOSED and nothing more is heard; a bridge never opens again.
Example
if (phantom.flash.bridge.state !== 'ready') await phantom.flash.bridge.whenReady();
console.log('bridge is', phantom.flash.bridge.state);allocate
allocate() => numberA number of this bridge's own, for something it will name in Flash (an overlay node).
Numbers come from the same counter as request ids and subscriptions, so one never meets another of this bridge's, nor another bridge's on the same game. Nothing is sent to Flash: the number means something only once a request such as draw uses it. close ends everything numbered by this bridge, allocated numbers included.
Returns
number. A fresh number, never given before by this bridge.
Example
const node = phantom.flash.bridge.allocate();
await phantom.flash.bridge.draw(node, { type: 'text', x: 20, y: 20, text: 'Hello, Jamaa' });answer
answer(callback: number, value?: FlashArgument) => Promise<void>What a function you passed into Flash gives the game back when the game calls it.
A function crosses as a callback, and the game hearing an answer from TypeScript would mean waiting for it, which inside one of the game's own calls it cannot do. So the answer is kept in Flash and read at each call: set it before the game calls, change it between calls, and your listener still hears every call as it happens. Without value the function answers nothing again, and so does undefined. phantom.flash.answering does this for you. Only a loader that lists the answer feature keeps answers.
Parameters
| Name | Type | |
|---|---|---|
callback | number | The callback's number, as encode gave it ({ $callback }). |
value optional | FlashArgument | What the game reads from the call. Left out, the call answers nothing. |
Returns
Promise<void>
Types
Example
const encoded = phantom.flash.bridge.encode((command) => console.log('the game heard', command));
await phantom.flash.bridge.answer((encoded as { $callback: number }).$callback, true);bounds
bounds(node: number) => Promise<OverlayBounds>Where an overlay node is now and how big it is, children and all.
The raw form of node.bounds(), which is what to use. The box is in the coordinates of whatever the node is drawn in, so a node in a panel is placed from the panel's content. Only a loader that lists the measure feature answers it.
Parameters
| Name | Type | |
|---|---|---|
node | number | The node's number, from allocate. |
Returns
Promise<OverlayBounds>. Its box, and whether it is shown.
Example
const node = phantom.flash.bridge.allocate();
await phantom.flash.bridge.draw(node, { type: 'text', x: 12, y: 12, text: 'Waiting' });
console.log('it takes up', await phantom.flash.bridge.bounds(node));chain
chain(steps: readonly ChainStep[]) => Promise<ChainResult>A few steps run one after another in the game, in one frame, each able to use what the one before it answered.
The raw form of phantom.flash.chain. A batch sends several requests at once, but each of them names what it works on up front, so anything whose second step is about the first step's answer cannot go in one: the client's definition of a den item is fetched by a call and its fields are then read off the object that call returned, which is a call, a read and a release for every item, with the object crossing as a handle in between. A chain is that with the object never leaving Flash.
A step names its place as any request does, or as { kind: 'step', step: n } for what an earlier step answered, where a negative n counts back from the step naming it. An argument may be { $step: n, path: [...] } anywhere a value goes. A step's answer crosses as the name of its class unless the step asks to hold it, so a chain hands over only the object it is asked for. An if on a step stops the chain where it does not hold, and stopped says which step that was.
Nothing of the game's happens between two steps, so a value one step writes is the value the next step's call reads. Only a loader that lists the chain feature can be asked.
Parameters
| Name | Type | |
|---|---|---|
steps | readonly ChainStep[] | The steps, in order. At most 32. See ChainStep. |
Returns
Promise<ChainResult>. Each step's value in order, and the step an if stopped the chain at, or null.
Example
const items = phantom.flash.bridge.definition('den.DenItemManager');
const { values } = await phantom.flash.bridge.chain([
{ do: 'call', from: items.target, path: ['getDenItemDef'], args: [1234] },
{
do: 'collect',
from: { kind: 'step', step: -1 },
path: [],
spec: { fields: [{ name: 'name', path: ['name'] }] },
},
]);
console.log('the definition reads', values[1]);close
close() => voidStops the bridge: every waiting request fails, and nothing more is heard. The loader is told to end everything this bridge started there (listeners, hooks and overrides, watches, overlay nodes and the client objects they replaced, highlights, styles, keys), if it can still be told.
Only a loader that lists dispose can be told; handles it gave are not released. Closing twice does nothing. A script's phantom.flash.bridge is the script's own, so closing it ends the script's reach into the game for good: Phantom closes it when the script stops, and a script rarely needs to.
Example
phantom.flash.bridge.onUncaught((error) => {
console.error('Giving up on the game:', error.message);
phantom.flash.bridge.close();
});collect
collect(entry: WatchEntry, spec: CollectSpec) => Promise<CollectAnswer>The same members read out of every item of a list in Flash, in one answer.
The raw form of phantom.flash.collect. The place is a list of its own: an array, a vector, a dictionary, a display object's children, or one object, which is read as a list of one. Every field is read from every item; one that is not there, or that throws, comes back as null rather than failing the read. Only a loader that lists the collect feature can be asked.
Parameters
| Name | Type | |
|---|---|---|
entry | WatchEntry | Where the list is, as FlashReference.entry gives it. |
spec | CollectSpec | The fields to read, and how many items: fields, skip, limit and handles. |
Returns
Promise<CollectAnswer>. The rows read, and how many items the list held.
Example
const players = phantom.flash.bridge.definition('avatar.AvatarManager').at('avatarList');
const { items, total } = await phantom.flash.bridge.collect(players.entry, {
fields: [{ name: 'name', path: ['userName'] }, { name: 'x', path: ['avatarWorldView', 'x'] }],
limit: 50,
});
console.log(`${items.length} of ${total} players`, items[0]?.name);construct
construct<Value extends FlashValue = FlashHandle>(className: string, ...args: FlashArgument[]) => Promise<Value>Makes an object of one of Flash's or the client's classes, and resolves with a handle to it.
The class is looked up by qualified name in the client's application domain, and new is run there with the arguments decoded: plain data as itself, handles as their objects, and functions as functions Flash can call back. Up to 32 arguments, which covers every constructor in the client. It fails with FLASH when the name is not a class or the constructor throws. The object stays alive in Flash until the handle is released, so release what you no longer need.
Parameters
| Name | Type | |
|---|---|---|
className | string | The class's qualified name, such as flash.geom.Point. |
args | FlashArgument[] | The constructor's arguments, in order. |
Returns
Promise<Value>. A handle to the new object.
Types
Example
const point = await phantom.flash.bridge.construct('flash.geom.Point', 300, 200);
const view = phantom.flash.bridge.definition('avatar.AvatarManager').at('playerAvatarWorldView');
console.log(await view.at('globalToLocal').call(point));
await phantom.flash.bridge.release([point]);decode
decode(value: BridgeValue) => FlashValueTurns what crossed the bridge into a value: handles become FlashHandles.
{ $undefined: true } becomes undefined, and arrays and objects are decoded throughout; an object read without handles ({ $object: className }) stays as it is. For the raw values in a subscription's messages, such as a hook's arguments; answers to requests are decoded already.
Parameters
| Name | Type | |
|---|---|---|
value | BridgeValue | A value as it crossed the bridge. |
Returns
FlashValue. The value, with handles as FlashHandles.
Types
Example
const table = await phantom.flash.bridge.hookTable();
const entry = table.hooks.find((hook) => hook.className === 'avatar.AvatarManager' && hook.member === 'createAvatar')!;
await phantom.flash.bridge.hook(entry.id, {
observe: ['enter'], capture: { args: [0, 6], self: [], value: false, handles: false }, when: null,
first: null, every: null, skip: null, replaceArgs: [], replaceReturn: null, perFrame: 20,
}, (message) => {
if (message.kind === 'hook') console.log((message.args ?? []).map((arg) => phantom.flash.bridge.decode(arg)));
});definition
definition(name: string) => FlashReferenceA class, or a package-level function or variable, by qualified name.
Nothing is sent until the reference is read, written, called or listened to, and the name is looked up in the client's application domain then, so a wrong name fails that request, not this call. From a class, at reaches its static members. phantom.flash.use and phantom.flash.global give the same place typed from the client's declarations.
Parameters
| Name | Type | |
|---|---|---|
name | string | The qualified name with dots, such as avatar.AvatarManager or gMainFrame. |
Returns
FlashReference. An untyped reference to the definition.
Example
const avatars = phantom.flash.bridge.definition('avatar.AvatarManager');
const myId = await avatars.at('playerSfsUserId').get<number>();
const name = await phantom.flash.bridge.definition('gMainFrame').at('userInfo', 'myUserName').get();
console.log(myId, name);draw
draw(node: number, spec: OverlaySpec | Partial<OverlaySpec>) => Promise<void>Makes or changes overlay node node.
The raw form of phantom.flash.overlay, whose shapes and widgets are what to use. A node that does not exist yet is made from spec, which must give its type; for one that does, only the fields given change, and null resets one. Its mouse and widget events arrive under its number (onSubscription), and it lasts until erase or until the bridge closes.
Parameters
| Name | Type | |
|---|---|---|
node | number | The node's number, from allocate. |
spec | OverlaySpec | Partial<OverlaySpec> | What the node is, or the fields that change. See OverlaySpec. |
Returns
Promise<void>
Example
const bridge = phantom.flash.bridge;
const node = bridge.allocate();
await bridge.draw(node, { type: 'text', x: 12, y: 12, text: 'Gems: ...' });
await bridge.draw(node, { text: 'Gems: 1200' });drive
drive(spec: DriveSpec, listener?: ((error: string) => void) | undefined) => Promise<Unsubscribe>Values Flash holds in place itself, frame after frame. Resolves with what ends the drive.
The raw form of phantom.flash.hold, which is what to use. A member the client sets every frame is back to the client's own value before a write from here could land, so the write is kept in Flash instead and made there, with nothing crossing the bridge. Each write's value is a HookSource that works outside a call: constant, class or place. while holds it back on the frames its condition does not hold, once writes once, and restore puts back what was there when the drive ends. A drive whose write throws is turned off, and listener hears why. Only a loader that lists the drive feature holds values.
Parameters
| Name | Type | |
|---|---|---|
spec | DriveSpec | What to write, where, how often, while what, and whether to put it back. See DriveSpec. |
listener optional | ((error: string) => void) | undefined | Called if Flash turns the drive off because a write failed. |
Returns
Promise<Unsubscribe>. A function that ends the drive.
Types
Example
const avatars = phantom.flash.bridge.definition('avatar.AvatarManager');
const stop = await phantom.flash.bridge.drive({
writes: [{ ...avatars.at('playerAvatarWorldView', 'scaleX').entry, value: { from: 'constant', value: 2 } }],
everyFrames: 1, while: null, restore: true, once: false,
});
// later: await stop();encode
encode(value: FlashArgument) => BridgeArgumentTurns an argument into what crosses the bridge.
undefined becomes { $undefined: true }, a handle { $handle }, a FlashNew { $new }, and a function { $callback }, kept here so Flash can call it until the bridge closes. Arrays and objects are encoded throughout. For building a request by hand, such as a hook rule's constant.
One thing to know about a function that crosses: the loader hands the client a function(...args) of its own, whose Function.length in ActionScript is 0 however many arguments yours takes. A few of the client's own methods branch on that (ItemXtCommManager.shopListResponse reads callback.length == 3, PetManager.onPetFinderClose reads == 1), so a callback handed to one of those takes the branch meant for a nullary function and is called with different arguments. Read the method before handing it a callback.
Parameters
| Name | Type | |
|---|---|---|
value | FlashArgument | The value to encode. |
Returns
BridgeArgument. What crosses the bridge for it.
Types
Example
const greeting = phantom.flash.bridge.encode('Hello from Phantom');
const callback = phantom.flash.bridge.encode((...args) => console.log('Flash called back with', args));
console.log(greeting, callback);encodeRequest
encodeRequest(request: Omit<{ id: number; op: "get"; target: BridgeTarget; path: string[]; }, "id"> | Omit<{ id: number; op: "set"; target: BridgeTarget; path: string[]; value: BridgeArgument; }, "id"> | Omit<{ id: number; op: "call"; target: BridgeTarget; path: string[]; args: BridgeArgument[]; }, "id"> | Omit<{ id: number; op: "construct"; className: string; args: BridgeArgument[]; }, "id"> | Omit<{ id: number; op: "describe"; target: BridgeTarget; path: string[]; }, "id"> | Omit<{ id: number; op: "listen"; target: BridgeTarget; path: string[]; event: string; listener: number; read?: string[][] | undefined; capture?: boolean | undefined; }, "id"> | Omit<{ id: number; op: "unlisten"; listener: number; }, "id"> | Omit<{ id: number; op: "release"; handles: number[]; }, "id"> | Omit<{ id: number; op: "batch"; requests: BridgeRequest[]; }, "id"> | Omit<{ id: number; op: "hookTable"; url?: string | undefined; }, "id"> | Omit<{ id: number; op: "hook"; registration: number; hook: number; rules: HookRules; }, "id"> | Omit<{ id: number; op: "rehook"; registration: number; rules: HookRules; resetCounts: boolean; }, "id"> | Omit<{ id: number; op: "unhook"; registration: number; }, "id"> | Omit<{ id: number; op: "hookStats"; }, "id"> | Omit<{ id: number; op: "watch"; watch: number; entries: WatchEntry[]; everyFrames: number; handles: boolean; }, "id"> | Omit<{ id: number; op: "unwatch"; watch: number; }, "id"> | Omit<{ id: number; op: "tick"; tick: number; everyFrames: number; }, "id"> | Omit<{ id: number; op: "untick"; tick: number; }, "id"> | Omit<{ id: number; op: "draw"; node: number; spec: OverlaySpec; }, "id"> | Omit<{ id: number; op: "erase"; nodes: number[]; }, "id"> | Omit<{ id: number; op: "measure"; text?: string | undefined; spans?: TextSpan[] | null | undefined; font?: OverlayFont | undefined; padding?: number | undefined; wrapWidth?: number | undefined; }, "id"> | Omit<{ id: number; op: "bounds"; node: number; }, "id"> | Omit<{ id: number; op: "highlight"; highlight: number; target: BridgeTarget; path: string[]; effect: HighlightEffect; }, "id"> | Omit<{ id: number; op: "unhighlight"; highlight: number; }, "id"> | Omit<{ id: number; op: "render"; render: number; spec: RenderSpec; }, "id"> | Omit<{ id: number; op: "unrender"; render: number; }, "id"> | Omit<{ id: number; op: "renderStats"; render: number; }, "id"> | Omit<{ id: number; op: "shader"; shader: number; spec: ShaderPassSpec; }, "id"> | Omit<{ id: number; op: "unshader"; shader: number; }, "id"> | Omit<{ id: number; op: "shaderStats"; shader: number; }, "id"> | Omit<{ id: number; op: "invokeOriginal"; registration: number; token: number; arguments: BridgeArgument[] | null; }, "id"> | Omit<{ id: number; op: "overrideDone"; registration: number; token: number; }, "id"> | Omit<{ id: number; op: "style"; style: number; target: BridgeTarget; path: string[]; format: TextStyle; }, "id"> | Omit<{ id: number; op: "unstyle"; style: number; }, "id"> | Omit<{ id: number; op: "key"; key: number; spec: KeySpec; }, "id"> | Omit<{ id: number; op: "unkey"; key: number; }, "id"> | Omit<{ id: number; op: "note"; table: number; verb: "write"; target: BridgeTarget; path: string[]; value: BridgeArgument; }, "id"> | Omit<{ id: number; op: "note"; table: number; verb: "read" | "has" | "forget"; target: BridgeTarget; path: string[]; }, "id"> | Omit<{ id: number; op: "note"; table: number; verb: "keys" | "clear"; }, "id"> | Omit<{ id: number; op: "find"; target: BridgeTarget; path: string[]; spec: FindSpec; }, "id"> | Omit<{ id: number; op: "drive"; drive: number; spec: DriveSpec; }, "id"> | Omit<{ id: number; op: "undrive"; drive: number; }, "id"> | Omit<{ id: number; op: "track"; track: number; target: BridgeTarget; path: string[]; className: string; mode: TrackMode; everyFrames: number; }, "id"> | Omit<{ id: number; op: "untrack"; track: number; }, "id"> | Omit<{ id: number; op: "collect"; target: BridgeTarget; path: string[]; spec: CollectSpec; }, "id"> | Omit<{ id: number; op: "when"; condition: HookCondition; everyFrames?: number | undefined; timeoutMs?: number | undefined; reads?: WatchEntry[] | undefined; handles?: boolean | undefined; }, "id"> | Omit<{ id: number; op: "chain"; steps: ChainStep[]; }, "id"> | Omit<{ id: number; op: "snapshot"; target: BridgeTarget; path: string[]; scale: number; background: number | null; maxSize: number; }, "id"> | Omit<{ id: number; op: "answer"; callback: number; value?: BridgeArgument | undefined; }, "id"> | Omit<{ id: number; op: "dispose"; from: number; to: number; }, "id">) => stringA request encoded as it would be sent, with a number of this bridge's own, but not sent.
For handing a request to something that will send it later, or somewhere else: Phantom keeps a plugin's undo this way and sends it to the game once the plugin's frame has gone, so what the plugin changed in the client is put back whether or not the plugin's own cleanup ran. The number is this bridge's, so a host that only accepts a frame's own range accepts it.
Nothing is waited for and no answer is matched, so a request encoded this way must be one whose answer nobody needs.
Parameters
| Name | Type | |
|---|---|---|
request | Omit<{ id: number; op: "get"; target: BridgeTarget; path: string[]; }, "id"> | Omit<{ id: number; op: "set"; target: BridgeTarget; path: string[]; value: BridgeArgument; }, "id"> | Omit<{ id: number; op: "call"; target: BridgeTarget; path: string[]; args: BridgeArgument[]; }, "id"> | Omit<{ id: number; op: "construct"; className: string; args: BridgeArgument[]; }, "id"> | Omit<{ id: number; op: "describe"; target: BridgeTarget; path: string[]; }, "id"> | Omit<{ id: number; op: "listen"; target: BridgeTarget; path: string[]; event: string; listener: number; read?: string[][] | undefined; capture?: boolean | undefined; }, "id"> | Omit<{ id: number; op: "unlisten"; listener: number; }, "id"> | Omit<{ id: number; op: "release"; handles: number[]; }, "id"> | Omit<{ id: number; op: "batch"; requests: BridgeRequest[]; }, "id"> | Omit<{ id: number; op: "hookTable"; url?: string | undefined; }, "id"> | Omit<{ id: number; op: "hook"; registration: number; hook: number; rules: HookRules; }, "id"> | Omit<{ id: number; op: "rehook"; registration: number; rules: HookRules; resetCounts: boolean; }, "id"> | Omit<{ id: number; op: "unhook"; registration: number; }, "id"> | Omit<{ id: number; op: "hookStats"; }, "id"> | Omit<{ id: number; op: "watch"; watch: number; entries: WatchEntry[]; everyFrames: number; handles: boolean; }, "id"> | Omit<{ id: number; op: "unwatch"; watch: number; }, "id"> | Omit<{ id: number; op: "tick"; tick: number; everyFrames: number; }, "id"> | Omit<{ id: number; op: "untick"; tick: number; }, "id"> | Omit<{ id: number; op: "draw"; node: number; spec: OverlaySpec; }, "id"> | Omit<{ id: number; op: "erase"; nodes: number[]; }, "id"> | Omit<{ id: number; op: "measure"; text?: string | undefined; spans?: TextSpan[] | null | undefined; font?: OverlayFont | undefined; padding?: number | undefined; wrapWidth?: number | undefined; }, "id"> | Omit<{ id: number; op: "bounds"; node: number; }, "id"> | Omit<{ id: number; op: "highlight"; highlight: number; target: BridgeTarget; path: string[]; effect: HighlightEffect; }, "id"> | Omit<{ id: number; op: "unhighlight"; highlight: number; }, "id"> | Omit<{ id: number; op: "render"; render: number; spec: RenderSpec; }, "id"> | Omit<{ id: number; op: "unrender"; render: number; }, "id"> | Omit<{ id: number; op: "renderStats"; render: number; }, "id"> | Omit<{ id: number; op: "shader"; shader: number; spec: ShaderPassSpec; }, "id"> | Omit<{ id: number; op: "unshader"; shader: number; }, "id"> | Omit<{ id: number; op: "shaderStats"; shader: number; }, "id"> | Omit<{ id: number; op: "invokeOriginal"; registration: number; token: number; arguments: BridgeArgument[] | null; }, "id"> | Omit<{ id: number; op: "overrideDone"; registration: number; token: number; }, "id"> | Omit<{ id: number; op: "style"; style: number; target: BridgeTarget; path: string[]; format: TextStyle; }, "id"> | Omit<{ id: number; op: "unstyle"; style: number; }, "id"> | Omit<{ id: number; op: "key"; key: number; spec: KeySpec; }, "id"> | Omit<{ id: number; op: "unkey"; key: number; }, "id"> | Omit<{ id: number; op: "note"; table: number; verb: "write"; target: BridgeTarget; path: string[]; value: BridgeArgument; }, "id"> | Omit<{ id: number; op: "note"; table: number; verb: "read" | "has" | "forget"; target: BridgeTarget; path: string[]; }, "id"> | Omit<{ id: number; op: "note"; table: number; verb: "keys" | "clear"; }, "id"> | Omit<{ id: number; op: "find"; target: BridgeTarget; path: string[]; spec: FindSpec; }, "id"> | Omit<{ id: number; op: "drive"; drive: number; spec: DriveSpec; }, "id"> | Omit<{ id: number; op: "undrive"; drive: number; }, "id"> | Omit<{ id: number; op: "track"; track: number; target: BridgeTarget; path: string[]; className: string; mode: TrackMode; everyFrames: number; }, "id"> | Omit<{ id: number; op: "untrack"; track: number; }, "id"> | Omit<{ id: number; op: "collect"; target: BridgeTarget; path: string[]; spec: CollectSpec; }, "id"> | Omit<{ id: number; op: "when"; condition: HookCondition; everyFrames?: number | undefined; timeoutMs?: number | undefined; reads?: WatchEntry[] | undefined; handles?: boolean | undefined; }, "id"> | Omit<{ id: number; op: "chain"; steps: ChainStep[]; }, "id"> | Omit<{ id: number; op: "snapshot"; target: BridgeTarget; path: string[]; scale: number; background: number | null; maxSize: number; }, "id"> | Omit<{ id: number; op: "answer"; callback: number; value?: BridgeArgument | undefined; }, "id"> | Omit<{ id: number; op: "dispose"; from: number; to: number; }, "id"> | The request, without its number. |
Returns
string. The encoded message, ready for sendToFlash.
Types
Example
const put = phantom.flash.bridge.encodeRequest({
op: 'call',
target: { kind: 'definition', name: 'gui.GuiManager' },
path: ['avatarEditor', 'avEditor', 'colorTableColor1', 'destroy'],
args: [],
});
console.log('an undo, ready to send later:', put.length, 'characters');erase
erase(nodes: readonly number[]) => Promise<void>Removes overlay nodes, with what is drawn in them.
Nodes drawn in a group or panel go with it, and a node that stood in for one of the client's display objects shows that object again. Their event listeners here are dropped. Nothing is sent for an empty list or once the bridge closed.
Parameters
| Name | Type | |
|---|---|---|
nodes | readonly number[] | The numbers of the nodes to remove. |
Returns
Promise<void>
Example
const node = phantom.flash.bridge.allocate();
await phantom.flash.bridge.draw(node, { type: 'text', x: 12, y: 12, text: 'Back in 5 seconds' });
await sleep(5000);
await phantom.flash.bridge.erase([node]);find
find(entry: WatchEntry, spec: FindSpec) => Promise<FlashHandle[]>The live objects under a place, found by what they are rather than by where they are.
The raw form of phantom.flash.find. The search walks the display list from entry, nearest that place first, and resolves with handles to what matched, which are read, written, called and hooked like anything else. Release them when you are done. Every rule in spec has to hold; a search with no rules answers everything under the place, up to its limit. Only a loader that lists the find feature can search.
Parameters
| Name | Type | |
|---|---|---|
entry | WatchEntry | Where to start, as FlashReference.entry gives it. |
spec | FindSpec | Which objects to answer: class, name, text, whether they are shown, how deep to look and how many to answer. See FindSpec. |
Returns
Promise<FlashHandle[]>. Handles to the objects that matched.
Example
const buttons = await phantom.flash.bridge.find(phantom.flash.bridge.stage().entry, {
ofClass: 'flash.display.SimpleButton',
visibleOnly: true,
limit: 20,
});
console.log(buttons.length, 'buttons on screen');
await phantom.flash.bridge.release(buttons);gameClient
gameClient() => FlashReferenceThe game client's document object.
The instance of AJClient the loader loaded, as an untyped reference: nothing is sent until it is read, written, called or listened to, and a request before the client has loaded fails. phantom.flash.client() gives the same place, typed.
Returns
FlashReference. An untyped reference to the client's document object.
Example
const client = phantom.flash.bridge.gameClient();
console.log(await client.at('numChildren').get(), 'children on the client');highlight
highlight(entry: WatchEntry, effect: HighlightEffect) => Promise<Unsubscribe>Puts effect on the display object at entry. Resolves with what takes it off.
The raw form of phantom.flash.highlight, hide and recolor, which build the effect. The object is found when this is asked, and must be a display object. Filters are added to the object's own and put back when the game replaces them; a colour transform is combined with the object's and undone when removed, unless the game changed it meanwhile.
Parameters
| Name | Type | |
|---|---|---|
entry | WatchEntry | Where the display object is, as FlashReference.entry gives it. |
effect | HighlightEffect | The glow, colour matrix, colour transform, hiding and pulse to apply. See HighlightEffect. |
Returns
Promise<Unsubscribe>. A function that takes the effect off.
Types
Example
const view = phantom.flash.bridge.definition('avatar.AvatarManager').at('playerAvatarWorldView');
const off = await phantom.flash.bridge.highlight(view.entry, {
glow: { color: 0xffd700, alpha: 1, blur: 12, strength: 3, inner: false, knockout: false },
matrix: null, colorTransform: null, hide: false, pulse: { frames: 30, from: 0.3, to: 1 },
});hook
hook(hook: number, rules: HookRules, listener: (message: SubscriptionMessage) => void) => Promise<{ registration: number; stop: Unsubscribe; }>Registers hook hook with rules. Resolves with its registration number and what ends it.
The raw form of phantom.flash.hook and phantom.flash.override, which build the rules from names and are what to use. Every field of the rules is given; they run in Flash inside each call of the hooked member, and the messages they post (hook, override, hook-dropped and hook-failed) go to listener. Rules that throw turn the registration off and post hook-failed; the game carries on.
Parameters
| Name | Type | |
|---|---|---|
hook | number | The hook's number, the id of its entry in the hook table. The registration is put back when the game's page loads the client again, under a number of the new loader's, and the number this answers with goes on naming it: a long-running override keeps intercepting across a server switch. Calls it was holding when the page loaded went with the loader, tokens and all. |
rules | HookRules | What to do inside each call: when, what to capture, skip or replace. See HookRules. |
listener | (message: SubscriptionMessage) => void | Called with every message the registration posts: calls it observes or overrides, calls dropped over perFrame, and its rules failing. |
Returns
Promise<{ registration: number; stop: Unsubscribe; }>. The registration's number (what invokeOriginal and overrideDone name) and a function that ends it.
Types
Example
const table = await phantom.flash.bridge.hookTable();
const entry = table.hooks.find((hook) => hook.className === 'avatar.AvatarManager' && hook.member === 'createAvatar')!;
const { stop } = await phantom.flash.bridge.hook(entry.id, {
observe: ['enter'], capture: { args: [6], self: [], value: false, handles: false },
when: null, first: null, every: null, skip: null, replaceArgs: [], replaceReturn: null, perFrame: 20,
}, (message) => { if (message.kind === 'hook') console.log(message.args?.[0], 'arrived'); });hookTable
hookTable(url?: string | undefined) => Promise<HookTable>The hook table, loaded by the loader from url, or from where the page's flashvars say.
The table lists every method, getter and setter Phantom's rewrite of the client can hook, with the number hook takes. The loader keeps what it loaded and answers from it again, loading anew only for a different url. It fails when neither url nor the page named a table, or it could not be loaded. phantom.flash.hooks() gives it indexed by name, and caches it.
Parameters
| Name | Type | |
|---|---|---|
url optional | string | undefined | Where to load the table from; left out, the one the loader has or the page named. |
Returns
Promise<HookTable>. The table, with where it was read from.
Example
const table = await phantom.flash.bridge.hookTable();
const avatars = table.hooks.filter((hook) => hook.className === 'avatar.AvatarManager');
console.log(`${table.count} hooks, ${avatars.length} on AvatarManager`);invokeOriginal
invokeOriginal(registration: number, token: number, args: readonly FlashArgument[] | null) => Promise<FlashValue>Runs the original method for an overridden call, with its own arguments or with args, and resolves with what it returned. See HookRules.override.
For a registration made by hook with override: true, when an override message arrives. It may be asked more than once for the same call, until overrideDone. The registration does not intercept this invocation; registrations made after it still apply. What the method returns reaches here only: the client was given the rules' skip value when the call was intercepted. phantom.flash.override does all of this for you.
Parameters
| Name | Type | |
|---|---|---|
registration | number | The override's registration number, as hook resolved with it. |
token | number | The intercepted call's token, from its override message. |
args | readonly FlashArgument[] | null | The arguments to run it with, all of them in order; null for the call's own. |
Returns
Promise<FlashValue>. What the original method returned.
Types
Example
const bridge = phantom.flash.bridge;
const table = await bridge.hookTable();
const entry = table.hooks.find((hook) => hook.className === 'avatar.AvatarManager' && hook.member === 'createAvatar')!;
const { registration } = await bridge.hook(entry.id, {
observe: [], capture: { args: [], self: [], value: false, handles: false }, when: null, first: null,
every: null, skip: null, replaceArgs: [], replaceReturn: null, perFrame: 50, override: true,
}, async (message) => {
if (message.kind !== 'override') return;
await sleep(500); // every avatar arrives half a second late
await bridge.invokeOriginal(registration, message.token, null);
await bridge.overrideDone(registration, message.token);
});listen
listen(reference: FlashReference, event: string, listener: FlashEventListener, options?: { read?: readonly (readonly string[])[] | undefined; capture?: boolean | undefined; }) => Promise<() => Promise<void>>Listens for an event on an event dispatcher in Flash, and resolves with what stops listening.
The object at reference is found when this is asked, so a reference by path listens to the object there now, not to whatever is there later. It must be an IEventDispatcher, or this fails with FLASH. Each event crosses with its class name and its readable properties, objects among them as handles. FlashReference.listen does the same from the reference.
Those handles are released as soon as the listener returns, so an event nobody looks at holds nothing in the game. To hold one longer, pass it to keep, the listener's second argument, before returning, and release it when done. A listener that only needs to know the event happened passes read: []: the event then crosses as { className, type, read: [] } and nothing is held at all (a loader from before capturePaths describes it anyway, and its handles are released the same way).
With read, the paths named are read from the event while it is being dispatched, and the event crosses as { className, type, read } with the values in the order asked and nothing held. That is the way to know what was pressed: by the time a question could be asked back across the bridge, the client has often torn down what the event was about. capture listens on the way down to the target rather than on the way back up, so a press is heard even when one of the client's own handlers stops it. Both need a loader that lists capturePaths, except a read of the event's own members ([['stageX'], ['stageY']]), which an older loader answers from its description of the event, in the same shape.
Parameters
| Name | Type | |
|---|---|---|
reference | FlashReference | The event dispatcher to listen to: the stage, a display object, a handle. |
event | string | The event's type, such as click, mouseDown or enterFrame. |
listener | FlashEventListener | Called with each event, as an object of its className and public properties, or of its className, type and read when paths were asked for, and with keep, to hold one of its handles past the listener's return. |
options optional | { read?: readonly (readonly string[])[] | undefined; capture?: boolean | undefined; } | read, the paths to read from the event as it is dispatched; capture, to listen on the way down. |
Returns
Promise<() => Promise<void>>. A function that stops listening; calling it again, or after the bridge closed, does nothing.
Example 1
const bridge = phantom.flash.bridge;
const stop = await bridge.listen(bridge.stage(), 'mouseDown', (event) => {
const { stageX, stageY } = event as { stageX: number; stageY: number };
console.log('Clicked at', stageX, stageY);
});
// Holding what was clicked past the listener: kept, and released by the caller later.
const clicked: unknown[] = [];
await bridge.listen(bridge.stage(), 'click', (event, keep) => {
const { target } = event as { target: Parameters<typeof keep>[0] | null };
if (target) clicked.push(keep(target));
});Example 2
const bridge = phantom.flash.bridge;
const stop = await bridge.listen(
bridge.stage(),
'mouseDown',
(event) => console.log('pressed', (event as { read: unknown[] }).read[0]),
{ read: [['target', 'name']], capture: true }
);measure
measure(spec: { text?: string | undefined; spans?: TextSpan[] | null | undefined; font?: OverlayFont | undefined; padding?: number | undefined; wrapWidth?: number | undefined; }) => Promise<OverlayMetrics>How big text would be drawn, without drawing it.
The raw form of phantom.flash.overlay.measure, which is what to use. Flash makes the field the way a text node does and reports what it takes up, so the numbers are the ones that node would have. Only a loader that lists the measure feature answers it.
Parameters
| Name | Type | |
|---|---|---|
spec | { text?: string | undefined; spans?: TextSpan[] | null | undefined; font?: OverlayFont | undefined; padding?: number | undefined; wrapWidth?: number | undefined; } | The text or spans to measure, its font and padding, and wrapWidth to wrap it at. |
Returns
Promise<OverlayMetrics>. Its width, height and how many lines it is drawn on.
Example
const size = await phantom.flash.bridge.measure({ text: 'Gems: 1200', font: { family: '_sans', size: 12, color: 0xffffff, bold: true, italic: false } });
console.log('the label would be', size.width, 'by', size.height);note
note(table: number, verb: NoteVerb, entry?: WatchEntry | undefined, value?: FlashArgument) => Promise<FlashValue>One table of notes: data of your own, kept in Flash beside the game's own objects.
The raw form of phantom.flash.notes, which is what to use. A table is numbered by allocate, keyed by the object itself and held weakly, so a note never keeps the game's object alive. write needs entry and value; read, has and forget need entry; keys answers the objects the table holds notes beside, and clear drops the table. Only a loader that lists the notes feature has them.
Parameters
| Name | Type | |
|---|---|---|
table | number | The table's number, from allocate. |
verb | NoteVerb | What to do with it: write, read, has, forget, keys or clear. |
entry optional | WatchEntry | undefined | Where the object is, as FlashReference.entry gives it. Not for keys or clear. |
value optional | FlashArgument | What to keep, for write. |
Returns
Promise<FlashValue>. What was kept (read), whether there was anything (has, forget), the objects the table holds notes beside (keys), or nothing.
Types
Example
const table = phantom.flash.bridge.allocate();
const me = phantom.flash.bridge.definition('avatar.AvatarManager').at('playerAvatar');
await phantom.flash.bridge.note(table, 'write', me.entry, Date.now());
console.log('first seen at', await phantom.flash.bridge.note(table, 'read', me.entry));overrideDone
overrideDone(registration: number, token: number) => Promise<void>Lets go of what Flash holds for an overridden call. Nothing to do once the bridge closed.
Ask it once you are finished with a call an override intercepted, whether or not the original ran: until then Flash holds the call's object and arguments, and invokeOriginal may still be asked. Objects handed over only for the call are released with it.
Parameters
| Name | Type | |
|---|---|---|
registration | number | The override's registration number, as hook resolved with it. |
token | number | The intercepted call's token, from its override message. |
Returns
Promise<void>
Example
const bridge = phantom.flash.bridge;
const table = await bridge.hookTable();
const entry = table.hooks.find((hook) => hook.className === 'avatar.AvatarManager' && hook.member === 'createAvatar')!;
const { registration } = await bridge.hook(entry.id, {
observe: [], capture: { args: [], self: [], value: false, handles: false }, when: null, first: null,
every: null, skip: null, replaceArgs: [], replaceReturn: null, perFrame: 50, override: true,
}, (message) => {
// Swallow the call: never run the original, and let Flash forget it.
if (message.kind === 'override') void bridge.overrideDone(registration, message.token);
});release
release(handles: readonly FlashHandle[]) => Promise<void>Lets Flash forget objects it handed over as handles, so they can be collected.
An object handed over as a handle (by a read, a call, construct, a watch with handles) stays reachable in Flash until it is released or the game closes; closing the bridge does not release them. After this, using the handle fails. Nothing is sent for an empty list or once the bridge closed. FlashHandle.release releases one.
Parameters
| Name | Type | |
|---|---|---|
handles | readonly FlashHandle[] | The handles to let go of. |
Returns
Promise<void>
Example
const point = await phantom.flash.bridge.construct('flash.geom.Point', 10, 20);
console.log(await point.at('length').get());
await phantom.flash.bridge.release([point]);render
render(spec: RenderSpec) => Promise<RenderAnswer & { stop: Unsubscribe; }>Holds render settings on the tree under a place: stage.quality, and smoothing, pixelSnapping and cacheAsBitmap on everything found under it. Resolves with what the hold is doing and the way to end it.
The raw form of phantom.render. The client writes every one of these itself, so a hold is what is wanted rather than a set: the loader walks the tree again every everyFrames frames and sets back anything the client has put back. What was found the first time is remembered and written back when the hold ends.
The hold is put back by itself after the game's page loads the client again, since it names a place rather than an object the loader was holding.
Parameters
| Name | Type | |
|---|---|---|
spec | RenderSpec | What to hold, and where to walk from. See RenderSpec. |
Returns
Promise<RenderAnswer & { stop: Unsubscribe; }>. What the first walk did, and stop, which puts it all back.
Types
Example
const held = await phantom.flash.bridge.render({
target: { kind: 'stage' }, path: [], quality: 'best', smoothing: true,
pixelSnapping: 'auto', cacheAsBitmap: null, depth: 24, everyFrames: 12, maxObjects: 4000,
});
console.log(`${held.held} objects smoothed`);
await held.stop();renderStats
renderStats(render: number) => Promise<RenderAnswer>What render hold render has done: how much of the tree the last walk looked at, how much it is holding, and whether it stopped at maxObjects before the tree ran out.
Parameters
| Name | Type | |
|---|---|---|
render | number | The hold's number. |
Returns
Promise<RenderAnswer>. What it has looked at and is holding. See RenderAnswer.
Example
const held = await phantom.flash.bridge.render({
target: { kind: 'stage' }, path: [], quality: 'best', smoothing: true,
pixelSnapping: null, cacheAsBitmap: null, depth: 24, everyFrames: 12, maxObjects: 4000,
});
await sleep(1000);
if ((await phantom.flash.bridge.renderStats(held.held)).capped) console.log('tree is larger than the cap');
await held.stop();request
request(request: Omit<{ id: number; op: "get"; target: BridgeTarget; path: string[]; }, "id"> | Omit<{ id: number; op: "set"; target: BridgeTarget; path: string[]; value: BridgeArgument; }, "id"> | Omit<{ id: number; op: "call"; target: BridgeTarget; path: string[]; args: BridgeArgument[]; }, "id"> | Omit<{ id: number; op: "construct"; className: string; args: BridgeArgument[]; }, "id"> | Omit<{ id: number; op: "describe"; target: BridgeTarget; path: string[]; }, "id"> | Omit<{ id: number; op: "listen"; target: BridgeTarget; path: string[]; event: string; listener: number; read?: string[][] | undefined; capture?: boolean | undefined; }, "id"> | Omit<{ id: number; op: "unlisten"; listener: number; }, "id"> | Omit<{ id: number; op: "release"; handles: number[]; }, "id"> | Omit<{ id: number; op: "batch"; requests: BridgeRequest[]; }, "id"> | Omit<{ id: number; op: "hookTable"; url?: string | undefined; }, "id"> | Omit<{ id: number; op: "hook"; registration: number; hook: number; rules: HookRules; }, "id"> | Omit<{ id: number; op: "rehook"; registration: number; rules: HookRules; resetCounts: boolean; }, "id"> | Omit<{ id: number; op: "unhook"; registration: number; }, "id"> | Omit<{ id: number; op: "hookStats"; }, "id"> | Omit<{ id: number; op: "watch"; watch: number; entries: WatchEntry[]; everyFrames: number; handles: boolean; }, "id"> | Omit<{ id: number; op: "unwatch"; watch: number; }, "id"> | Omit<{ id: number; op: "tick"; tick: number; everyFrames: number; }, "id"> | Omit<{ id: number; op: "untick"; tick: number; }, "id"> | Omit<{ id: number; op: "draw"; node: number; spec: OverlaySpec; }, "id"> | Omit<{ id: number; op: "erase"; nodes: number[]; }, "id"> | Omit<{ id: number; op: "measure"; text?: string | undefined; spans?: TextSpan[] | null | undefined; font?: OverlayFont | undefined; padding?: number | undefined; wrapWidth?: number | undefined; }, "id"> | Omit<{ id: number; op: "bounds"; node: number; }, "id"> | Omit<{ id: number; op: "highlight"; highlight: number; target: BridgeTarget; path: string[]; effect: HighlightEffect; }, "id"> | Omit<{ id: number; op: "unhighlight"; highlight: number; }, "id"> | Omit<{ id: number; op: "render"; render: number; spec: RenderSpec; }, "id"> | Omit<{ id: number; op: "unrender"; render: number; }, "id"> | Omit<{ id: number; op: "renderStats"; render: number; }, "id"> | Omit<{ id: number; op: "shader"; shader: number; spec: ShaderPassSpec; }, "id"> | Omit<{ id: number; op: "unshader"; shader: number; }, "id"> | Omit<{ id: number; op: "shaderStats"; shader: number; }, "id"> | Omit<{ id: number; op: "invokeOriginal"; registration: number; token: number; arguments: BridgeArgument[] | null; }, "id"> | Omit<{ id: number; op: "overrideDone"; registration: number; token: number; }, "id"> | Omit<{ id: number; op: "style"; style: number; target: BridgeTarget; path: string[]; format: TextStyle; }, "id"> | Omit<{ id: number; op: "unstyle"; style: number; }, "id"> | Omit<{ id: number; op: "key"; key: number; spec: KeySpec; }, "id"> | Omit<{ id: number; op: "unkey"; key: number; }, "id"> | Omit<{ id: number; op: "note"; table: number; verb: "write"; target: BridgeTarget; path: string[]; value: BridgeArgument; }, "id"> | Omit<{ id: number; op: "note"; table: number; verb: "read" | "has" | "forget"; target: BridgeTarget; path: string[]; }, "id"> | Omit<{ id: number; op: "note"; table: number; verb: "keys" | "clear"; }, "id"> | Omit<{ id: number; op: "find"; target: BridgeTarget; path: string[]; spec: FindSpec; }, "id"> | Omit<{ id: number; op: "drive"; drive: number; spec: DriveSpec; }, "id"> | Omit<{ id: number; op: "undrive"; drive: number; }, "id"> | Omit<{ id: number; op: "track"; track: number; target: BridgeTarget; path: string[]; className: string; mode: TrackMode; everyFrames: number; }, "id"> | Omit<{ id: number; op: "untrack"; track: number; }, "id"> | Omit<{ id: number; op: "collect"; target: BridgeTarget; path: string[]; spec: CollectSpec; }, "id"> | Omit<{ id: number; op: "when"; condition: HookCondition; everyFrames?: number | undefined; timeoutMs?: number | undefined; reads?: WatchEntry[] | undefined; handles?: boolean | undefined; }, "id"> | Omit<{ id: number; op: "chain"; steps: ChainStep[]; }, "id"> | Omit<{ id: number; op: "snapshot"; target: BridgeTarget; path: string[]; scale: number; background: number | null; maxSize: number; }, "id"> | Omit<{ id: number; op: "answer"; callback: number; value?: BridgeArgument | undefined; }, "id"> | Omit<{ id: number; op: "dispose"; from: number; to: number; }, "id">, options?: { timeoutMs?: number | undefined; }) => Promise<FlashValue>Sends a request and resolves with its value. Used by references; rarely needed directly.
For a request nothing else wraps, such as describe or hookStats. The id is added here. Requests made in the same microtask go as one batch when the loader lists batch. A request made before the loader is ready waits for it, up to the timeout, then fails with NOT_READY. It fails with TIMEOUT when no answer comes in time (5 seconds by default, 10 for a script's bridge), CLOSED when the bridge closes first, and FLASH when Flash threw.
Parameters
| Name | Type | |
|---|---|---|
request | Omit<{ id: number; op: "get"; target: BridgeTarget; path: string[]; }, "id"> | Omit<{ id: number; op: "set"; target: BridgeTarget; path: string[]; value: BridgeArgument; }, "id"> | Omit<{ id: number; op: "call"; target: BridgeTarget; path: string[]; args: BridgeArgument[]; }, "id"> | Omit<{ id: number; op: "construct"; className: string; args: BridgeArgument[]; }, "id"> | Omit<{ id: number; op: "describe"; target: BridgeTarget; path: string[]; }, "id"> | Omit<{ id: number; op: "listen"; target: BridgeTarget; path: string[]; event: string; listener: number; read?: string[][] | undefined; capture?: boolean | undefined; }, "id"> | Omit<{ id: number; op: "unlisten"; listener: number; }, "id"> | Omit<{ id: number; op: "release"; handles: number[]; }, "id"> | Omit<{ id: number; op: "batch"; requests: BridgeRequest[]; }, "id"> | Omit<{ id: number; op: "hookTable"; url?: string | undefined; }, "id"> | Omit<{ id: number; op: "hook"; registration: number; hook: number; rules: HookRules; }, "id"> | Omit<{ id: number; op: "rehook"; registration: number; rules: HookRules; resetCounts: boolean; }, "id"> | Omit<{ id: number; op: "unhook"; registration: number; }, "id"> | Omit<{ id: number; op: "hookStats"; }, "id"> | Omit<{ id: number; op: "watch"; watch: number; entries: WatchEntry[]; everyFrames: number; handles: boolean; }, "id"> | Omit<{ id: number; op: "unwatch"; watch: number; }, "id"> | Omit<{ id: number; op: "tick"; tick: number; everyFrames: number; }, "id"> | Omit<{ id: number; op: "untick"; tick: number; }, "id"> | Omit<{ id: number; op: "draw"; node: number; spec: OverlaySpec; }, "id"> | Omit<{ id: number; op: "erase"; nodes: number[]; }, "id"> | Omit<{ id: number; op: "measure"; text?: string | undefined; spans?: TextSpan[] | null | undefined; font?: OverlayFont | undefined; padding?: number | undefined; wrapWidth?: number | undefined; }, "id"> | Omit<{ id: number; op: "bounds"; node: number; }, "id"> | Omit<{ id: number; op: "highlight"; highlight: number; target: BridgeTarget; path: string[]; effect: HighlightEffect; }, "id"> | Omit<{ id: number; op: "unhighlight"; highlight: number; }, "id"> | Omit<{ id: number; op: "render"; render: number; spec: RenderSpec; }, "id"> | Omit<{ id: number; op: "unrender"; render: number; }, "id"> | Omit<{ id: number; op: "renderStats"; render: number; }, "id"> | Omit<{ id: number; op: "shader"; shader: number; spec: ShaderPassSpec; }, "id"> | Omit<{ id: number; op: "unshader"; shader: number; }, "id"> | Omit<{ id: number; op: "shaderStats"; shader: number; }, "id"> | Omit<{ id: number; op: "invokeOriginal"; registration: number; token: number; arguments: BridgeArgument[] | null; }, "id"> | Omit<{ id: number; op: "overrideDone"; registration: number; token: number; }, "id"> | Omit<{ id: number; op: "style"; style: number; target: BridgeTarget; path: string[]; format: TextStyle; }, "id"> | Omit<{ id: number; op: "unstyle"; style: number; }, "id"> | Omit<{ id: number; op: "key"; key: number; spec: KeySpec; }, "id"> | Omit<{ id: number; op: "unkey"; key: number; }, "id"> | Omit<{ id: number; op: "note"; table: number; verb: "write"; target: BridgeTarget; path: string[]; value: BridgeArgument; }, "id"> | Omit<{ id: number; op: "note"; table: number; verb: "read" | "has" | "forget"; target: BridgeTarget; path: string[]; }, "id"> | Omit<{ id: number; op: "note"; table: number; verb: "keys" | "clear"; }, "id"> | Omit<{ id: number; op: "find"; target: BridgeTarget; path: string[]; spec: FindSpec; }, "id"> | Omit<{ id: number; op: "drive"; drive: number; spec: DriveSpec; }, "id"> | Omit<{ id: number; op: "undrive"; drive: number; }, "id"> | Omit<{ id: number; op: "track"; track: number; target: BridgeTarget; path: string[]; className: string; mode: TrackMode; everyFrames: number; }, "id"> | Omit<{ id: number; op: "untrack"; track: number; }, "id"> | Omit<{ id: number; op: "collect"; target: BridgeTarget; path: string[]; spec: CollectSpec; }, "id"> | Omit<{ id: number; op: "when"; condition: HookCondition; everyFrames?: number | undefined; timeoutMs?: number | undefined; reads?: WatchEntry[] | undefined; handles?: boolean | undefined; }, "id"> | Omit<{ id: number; op: "chain"; steps: ChainStep[]; }, "id"> | Omit<{ id: number; op: "snapshot"; target: BridgeTarget; path: string[]; scale: number; background: number | null; maxSize: number; }, "id"> | Omit<{ id: number; op: "answer"; callback: number; value?: BridgeArgument | undefined; }, "id"> | Omit<{ id: number; op: "dispose"; from: number; to: number; }, "id"> | The request, without its id. See BridgeRequest. |
options optional | { timeoutMs?: number | undefined; } | timeoutMs, for the few requests the game is meant to take its time over: a when is answered when its wait ends, which is later than any other answer. |
Returns
Promise<FlashValue>. The answer, decoded: objects as FlashHandles.
Types
Example
const stats = await phantom.flash.bridge.request({ op: 'hookStats' });
const members = await phantom.flash.bridge.request({
op: 'describe', target: { kind: 'definition', name: 'avatar.AvatarManager' }, path: [],
});
console.log(stats, members);shader
shader(spec: ShaderPassSpec, onState?: ((answer: ShaderPassAnswer) => void) | undefined) => Promise<ShaderPassAnswer & { stop: Unsubscribe; }>Starts a Stage3D pass over the game: a shader of your own, running on the graphics card. Resolves with where the pass got to and the way to end it.
The raw form of phantom.render.shadeGpu, which assembles the AGAL and fills the spec. The programs are AGAL bytecode in base64; the loader decodes them, asks the player for a Context3D, uploads them, and from then on captures the game into a texture and draws it through them every frame.
It is normal for this to resolve with state: 'waiting'. The player refuses a context while the movie is not being drawn, and Phantom hides a game that is not the tab in front, so a pass started from the Scripts tool waits until the game is looked at. onState is called whenever that changes, which is why it is here rather than left to polling: the answer that matters ("there is no card, use the filters instead") can arrive seconds later, and can change back.
Nothing is hidden and nothing is drawn while the pass is not drawing, so a pass that never gets a context leaves the game exactly as it found it.
Parameters
| Name | Type | |
|---|---|---|
spec | ShaderPassSpec | The programs, their constants, what to capture and how often. See ShaderPassSpec. |
onState optional | ((answer: ShaderPassAnswer) => void) | undefined | Called with the pass's state each time it changes. |
Returns
Promise<ShaderPassAnswer & { stop: Unsubscribe; }>. What the pass is doing, and stop, which gives the context back and shows the game.
Types
Example
// AGAL bytecode, base64: Phantom's own fullscreen vertex program, and a fragment program that
// draws the captured frame back unchanged. `phantom.render.shadeGpu` assembles these from AGAL
// source, which is what to use unless the bytes are already in hand.
const pass = await phantom.flash.bridge.shader(
{
vertex: 'oAEAAAChAAAAAAAAAA8DAAAA5AAAAAAAAAAAAAAAAAAAAAAAAA8EAQAA5AAAAAAAAAAAAAAAAA==',
fragment: 'oAEAAAChASgAAAAAAA8CAAAA5AQAAAAAAAAABQAAEAAAAAAAAA8DAAAA5AIAAAAAAAAAAAAAAA==',
constants: [[0.5, 0.5, 0, 1]],
frameRegister: 1,
target: { kind: 'loader' },
path: [],
everyFrames: 1,
retryFrames: 48,
allowSoftware: false,
reportFrames: 120,
},
(answer) => console.log(answer.state, answer.driverInfo)
);
console.log(`${pass.captureMs}ms to capture of a ${pass.budgetMs}ms frame`);
await pass.stop();shaderStats
shaderStats(shader: number) => Promise<ShaderPassAnswer>What shader pass shader is doing and what it is costing, read now.
The same answer onState is handed, for a caller that wants the numbers rather than the news: the per-frame milliseconds move every frame and are not a state change, so nothing posts them.
Parameters
| Name | Type | |
|---|---|---|
shader | number | The pass's number. |
Returns
Promise<ShaderPassAnswer>. What it is doing and what each step costs. See ShaderPassAnswer.
Example
const now = await phantom.flash.bridge.shaderStats(1);
console.log(`${now.frames} frames, worst ${now.worstMs}ms of a ${now.budgetMs}ms frame`);snapshot
snapshot(entry: WatchEntry, { scale, background, maxSize, }?: { scale?: number | undefined; background?: number | null | undefined; maxSize?: number | undefined; }) => Promise<string>One of the game's display objects, drawn to a picture.
The raw form of phantom.flash.snapshot. Flash has no picture encoder of its own, so the loader writes the PNG itself and hands it back as a data: URL, which is a string that can be shown, saved or sent. What is drawn is the object's own content at its own size, filters and children included; the rotation and scale its parent gives it are not part of the picture. Only a loader that lists the snapshot feature can draw one.
Parameters
| Name | Type | |
|---|---|---|
entry | WatchEntry | Where the display object is, as FlashReference.entry gives it. |
__1 optional | { scale?: number | undefined; background?: number | null | undefined; maxSize?: number | undefined; } | scale, how much bigger or smaller than its own size; background, a colour behind it, or nothing for a picture with nothing behind it; maxSize, the most pixels on a side, which the scale is cut down to fit. |
Returns
Promise<string>. The picture as a data:image/png;base64, URL.
Example
const view = phantom.flash.bridge.definition('avatar.AvatarManager').at('playerAvatarWorldView');
const picture = await phantom.flash.bridge.snapshot(view.entry, { scale: 2, maxSize: 512 });
console.log(picture.slice(0, 32));stage
stage() => FlashReferenceFlash's stage, which the game is drawn on.
An untyped reference: nothing is sent until it is read, written, called or listened to. It is the root of every display object, the loader's overlay and the game client included, and where keyboard and mouse events arrive. phantom.flash.stage() gives the same place, typed.
Returns
FlashReference. An untyped reference to the stage.
Example
const stage = phantom.flash.bridge.stage();
const [width, height] = await Promise.all([stage.at('stageWidth').get(), stage.at('stageHeight').get()]);
console.log(`The game is ${width}x${height}`);style
style(entry: WatchEntry, format: TextStyle) => Promise<Unsubscribe>Styles the text field at entry. Resolves with what puts its look back.
The raw form of phantom.flash.style. The field is found when this is asked and must be a TextField, or this fails. Fields of format left out stay as the client has them, and text the client adds later is styled too.
Parameters
| Name | Type | |
|---|---|---|
entry | WatchEntry | Where the text field is, as FlashReference.entry gives it. |
format | TextStyle | The look to give it. See TextStyle. |
Returns
Promise<Unsubscribe>. A function that puts the field's look back.
Types
Example
const chat = phantom.flash.bridge.definition('gui.GuiManager').at('mainHud', 'chatHistTxt');
const plain = await phantom.flash.bridge.style(chat.entry, { font: 'Verdana', size: 13, color: 0xf5f5f5 });subscribe
subscribe(start: (number: number) => Omit<{ id: number; op: "get"; target: BridgeTarget; path: string[]; }, "id"> | Omit<{ id: number; op: "set"; target: BridgeTarget; path: string[]; value: BridgeArgument; }, "id"> | Omit<{ id: number; op: "call"; target: BridgeTarget; path: string[]; args: BridgeArgument[]; }, "id"> | Omit<{ id: number; op: "construct"; className: string; args: BridgeArgument[]; }, "id"> | Omit<{ id: number; op: "describe"; target: BridgeTarget; path: string[]; }, "id"> | Omit<{ id: number; op: "listen"; target: BridgeTarget; path: string[]; event: string; listener: number; read?: string[][] | undefined; capture?: boolean | undefined; }, "id"> | Omit<{ id: number; op: "unlisten"; listener: number; }, "id"> | Omit<{ id: number; op: "release"; handles: number[]; }, "id"> | Omit<{ id: number; op: "batch"; requests: BridgeRequest[]; }, "id"> | Omit<{ id: number; op: "hookTable"; url?: string | undefined; }, "id"> | Omit<{ id: number; op: "hook"; registration: number; hook: number; rules: HookRules; }, "id"> | Omit<{ id: number; op: "rehook"; registration: number; rules: HookRules; resetCounts: boolean; }, "id"> | Omit<{ id: number; op: "unhook"; registration: number; }, "id"> | Omit<{ id: number; op: "hookStats"; }, "id"> | Omit<{ id: number; op: "watch"; watch: number; entries: WatchEntry[]; everyFrames: number; handles: boolean; }, "id"> | Omit<{ id: number; op: "unwatch"; watch: number; }, "id"> | Omit<{ id: number; op: "tick"; tick: number; everyFrames: number; }, "id"> | Omit<{ id: number; op: "untick"; tick: number; }, "id"> | Omit<{ id: number; op: "draw"; node: number; spec: OverlaySpec; }, "id"> | Omit<{ id: number; op: "erase"; nodes: number[]; }, "id"> | Omit<{ id: number; op: "measure"; text?: string | undefined; spans?: TextSpan[] | null | undefined; font?: OverlayFont | undefined; padding?: number | undefined; wrapWidth?: number | undefined; }, "id"> | Omit<{ id: number; op: "bounds"; node: number; }, "id"> | Omit<{ id: number; op: "highlight"; highlight: number; target: BridgeTarget; path: string[]; effect: HighlightEffect; }, "id"> | Omit<{ id: number; op: "unhighlight"; highlight: number; }, "id"> | Omit<{ id: number; op: "render"; render: number; spec: RenderSpec; }, "id"> | Omit<{ id: number; op: "unrender"; render: number; }, "id"> | Omit<{ id: number; op: "renderStats"; render: number; }, "id"> | Omit<{ id: number; op: "shader"; shader: number; spec: ShaderPassSpec; }, "id"> | Omit<{ id: number; op: "unshader"; shader: number; }, "id"> | Omit<{ id: number; op: "shaderStats"; shader: number; }, "id"> | Omit<{ id: number; op: "invokeOriginal"; registration: number; token: number; arguments: BridgeArgument[] | null; }, "id"> | Omit<{ id: number; op: "overrideDone"; registration: number; token: number; }, "id"> | Omit<{ id: number; op: "style"; style: number; target: BridgeTarget; path: string[]; format: TextStyle; }, "id"> | Omit<{ id: number; op: "unstyle"; style: number; }, "id"> | Omit<{ id: number; op: "key"; key: number; spec: KeySpec; }, "id"> | Omit<{ id: number; op: "unkey"; key: number; }, "id"> | Omit<{ id: number; op: "note"; table: number; verb: "write"; target: BridgeTarget; path: string[]; value: BridgeArgument; }, "id"> | Omit<{ id: number; op: "note"; table: number; verb: "read" | "has" | "forget"; target: BridgeTarget; path: string[]; }, "id"> | Omit<{ id: number; op: "note"; table: number; verb: "keys" | "clear"; }, "id"> | Omit<{ id: number; op: "find"; target: BridgeTarget; path: string[]; spec: FindSpec; }, "id"> | Omit<{ id: number; op: "drive"; drive: number; spec: DriveSpec; }, "id"> | Omit<{ id: number; op: "undrive"; drive: number; }, "id"> | Omit<{ id: number; op: "track"; track: number; target: BridgeTarget; path: string[]; className: string; mode: TrackMode; everyFrames: number; }, "id"> | Omit<{ id: number; op: "untrack"; track: number; }, "id"> | Omit<{ id: number; op: "collect"; target: BridgeTarget; path: string[]; spec: CollectSpec; }, "id"> | Omit<{ id: number; op: "when"; condition: HookCondition; everyFrames?: number | undefined; timeoutMs?: number | undefined; reads?: WatchEntry[] | undefined; handles?: boolean | undefined; }, "id"> | Omit<{ id: number; op: "chain"; steps: ChainStep[]; }, "id"> | Omit<{ id: number; op: "snapshot"; target: BridgeTarget; path: string[]; scale: number; background: number | null; maxSize: number; }, "id"> | Omit<{ id: number; op: "answer"; callback: number; value?: BridgeArgument | undefined; }, "id"> | Omit<{ id: number; op: "dispose"; from: number; to: number; }, "id">, stop: (number: number) => Omit<{ id: number; op: "get"; target: BridgeTarget; path: string[]; }, "id"> | Omit<{ id: number; op: "set"; target: BridgeTarget; path: string[]; value: BridgeArgument; }, "id"> | Omit<{ id: number; op: "call"; target: BridgeTarget; path: string[]; args: BridgeArgument[]; }, "id"> | Omit<{ id: number; op: "construct"; className: string; args: BridgeArgument[]; }, "id"> | Omit<{ id: number; op: "describe"; target: BridgeTarget; path: string[]; }, "id"> | Omit<{ id: number; op: "listen"; target: BridgeTarget; path: string[]; event: string; listener: number; read?: string[][] | undefined; capture?: boolean | undefined; }, "id"> | Omit<{ id: number; op: "unlisten"; listener: number; }, "id"> | Omit<{ id: number; op: "release"; handles: number[]; }, "id"> | Omit<{ id: number; op: "batch"; requests: BridgeRequest[]; }, "id"> | Omit<{ id: number; op: "hookTable"; url?: string | undefined; }, "id"> | Omit<{ id: number; op: "hook"; registration: number; hook: number; rules: HookRules; }, "id"> | Omit<{ id: number; op: "rehook"; registration: number; rules: HookRules; resetCounts: boolean; }, "id"> | Omit<{ id: number; op: "unhook"; registration: number; }, "id"> | Omit<{ id: number; op: "hookStats"; }, "id"> | Omit<{ id: number; op: "watch"; watch: number; entries: WatchEntry[]; everyFrames: number; handles: boolean; }, "id"> | Omit<{ id: number; op: "unwatch"; watch: number; }, "id"> | Omit<{ id: number; op: "tick"; tick: number; everyFrames: number; }, "id"> | Omit<{ id: number; op: "untick"; tick: number; }, "id"> | Omit<{ id: number; op: "draw"; node: number; spec: OverlaySpec; }, "id"> | Omit<{ id: number; op: "erase"; nodes: number[]; }, "id"> | Omit<{ id: number; op: "measure"; text?: string | undefined; spans?: TextSpan[] | null | undefined; font?: OverlayFont | undefined; padding?: number | undefined; wrapWidth?: number | undefined; }, "id"> | Omit<{ id: number; op: "bounds"; node: number; }, "id"> | Omit<{ id: number; op: "highlight"; highlight: number; target: BridgeTarget; path: string[]; effect: HighlightEffect; }, "id"> | Omit<{ id: number; op: "unhighlight"; highlight: number; }, "id"> | Omit<{ id: number; op: "render"; render: number; spec: RenderSpec; }, "id"> | Omit<{ id: number; op: "unrender"; render: number; }, "id"> | Omit<{ id: number; op: "renderStats"; render: number; }, "id"> | Omit<{ id: number; op: "shader"; shader: number; spec: ShaderPassSpec; }, "id"> | Omit<{ id: number; op: "unshader"; shader: number; }, "id"> | Omit<{ id: number; op: "shaderStats"; shader: number; }, "id"> | Omit<{ id: number; op: "invokeOriginal"; registration: number; token: number; arguments: BridgeArgument[] | null; }, "id"> | Omit<{ id: number; op: "overrideDone"; registration: number; token: number; }, "id"> | Omit<{ id: number; op: "style"; style: number; target: BridgeTarget; path: string[]; format: TextStyle; }, "id"> | Omit<{ id: number; op: "unstyle"; style: number; }, "id"> | Omit<{ id: number; op: "key"; key: number; spec: KeySpec; }, "id"> | Omit<{ id: number; op: "unkey"; key: number; }, "id"> | Omit<{ id: number; op: "note"; table: number; verb: "write"; target: BridgeTarget; path: string[]; value: BridgeArgument; }, "id"> | Omit<{ id: number; op: "note"; table: number; verb: "read" | "has" | "forget"; target: BridgeTarget; path: string[]; }, "id"> | Omit<{ id: number; op: "note"; table: number; verb: "keys" | "clear"; }, "id"> | Omit<{ id: number; op: "find"; target: BridgeTarget; path: string[]; spec: FindSpec; }, "id"> | Omit<{ id: number; op: "drive"; drive: number; spec: DriveSpec; }, "id"> | Omit<{ id: number; op: "undrive"; drive: number; }, "id"> | Omit<{ id: number; op: "track"; track: number; target: BridgeTarget; path: string[]; className: string; mode: TrackMode; everyFrames: number; }, "id"> | Omit<{ id: number; op: "untrack"; track: number; }, "id"> | Omit<{ id: number; op: "collect"; target: BridgeTarget; path: string[]; spec: CollectSpec; }, "id"> | Omit<{ id: number; op: "when"; condition: HookCondition; everyFrames?: number | undefined; timeoutMs?: number | undefined; reads?: WatchEntry[] | undefined; handles?: boolean | undefined; }, "id"> | Omit<{ id: number; op: "chain"; steps: ChainStep[]; }, "id"> | Omit<{ id: number; op: "snapshot"; target: BridgeTarget; path: string[]; scale: number; background: number | null; maxSize: number; }, "id"> | Omit<{ id: number; op: "answer"; callback: number; value?: BridgeArgument | undefined; }, "id"> | Omit<{ id: number; op: "dispose"; from: number; to: number; }, "id">, listener: (message: SubscriptionMessage) => void) => Promise<Unsubscribe>Starts something in Flash that posts messages under a number (a hook, a watch, a key), and resolves with what stops it. start and stop make the requests for the number given.
The building block under hook, watch, tick, highlight, style and onKey, for a request they do not wrap. A number is allocated, start(number) is sent, and the messages posted under that number go to listener until it is stopped. If the start request fails, nothing is kept and the promise rejects. Stopping sends stop(number), once, and not after the bridge closed (closing ends it in Flash anyway).
Parameters
| Name | Type | |
|---|---|---|
start | (number: number) => Omit<{ id: number; op: "get"; target: BridgeTarget; path: string[]; }, "id"> | Omit<{ id: number; op: "set"; target: BridgeTarget; path: string[]; value: BridgeArgument; }, "id"> | Omit<{ id: number; op: "call"; target: BridgeTarget; path: string[]; args: BridgeArgument[]; }, "id"> | Omit<{ id: number; op: "construct"; className: string; args: BridgeArgument[]; }, "id"> | Omit<{ id: number; op: "describe"; target: BridgeTarget; path: string[]; }, "id"> | Omit<{ id: number; op: "listen"; target: BridgeTarget; path: string[]; event: string; listener: number; read?: string[][] | undefined; capture?: boolean | undefined; }, "id"> | Omit<{ id: number; op: "unlisten"; listener: number; }, "id"> | Omit<{ id: number; op: "release"; handles: number[]; }, "id"> | Omit<{ id: number; op: "batch"; requests: BridgeRequest[]; }, "id"> | Omit<{ id: number; op: "hookTable"; url?: string | undefined; }, "id"> | Omit<{ id: number; op: "hook"; registration: number; hook: number; rules: HookRules; }, "id"> | Omit<{ id: number; op: "rehook"; registration: number; rules: HookRules; resetCounts: boolean; }, "id"> | Omit<{ id: number; op: "unhook"; registration: number; }, "id"> | Omit<{ id: number; op: "hookStats"; }, "id"> | Omit<{ id: number; op: "watch"; watch: number; entries: WatchEntry[]; everyFrames: number; handles: boolean; }, "id"> | Omit<{ id: number; op: "unwatch"; watch: number; }, "id"> | Omit<{ id: number; op: "tick"; tick: number; everyFrames: number; }, "id"> | Omit<{ id: number; op: "untick"; tick: number; }, "id"> | Omit<{ id: number; op: "draw"; node: number; spec: OverlaySpec; }, "id"> | Omit<{ id: number; op: "erase"; nodes: number[]; }, "id"> | Omit<{ id: number; op: "measure"; text?: string | undefined; spans?: TextSpan[] | null | undefined; font?: OverlayFont | undefined; padding?: number | undefined; wrapWidth?: number | undefined; }, "id"> | Omit<{ id: number; op: "bounds"; node: number; }, "id"> | Omit<{ id: number; op: "highlight"; highlight: number; target: BridgeTarget; path: string[]; effect: HighlightEffect; }, "id"> | Omit<{ id: number; op: "unhighlight"; highlight: number; }, "id"> | Omit<{ id: number; op: "render"; render: number; spec: RenderSpec; }, "id"> | Omit<{ id: number; op: "unrender"; render: number; }, "id"> | Omit<{ id: number; op: "renderStats"; render: number; }, "id"> | Omit<{ id: number; op: "shader"; shader: number; spec: ShaderPassSpec; }, "id"> | Omit<{ id: number; op: "unshader"; shader: number; }, "id"> | Omit<{ id: number; op: "shaderStats"; shader: number; }, "id"> | Omit<{ id: number; op: "invokeOriginal"; registration: number; token: number; arguments: BridgeArgument[] | null; }, "id"> | Omit<{ id: number; op: "overrideDone"; registration: number; token: number; }, "id"> | Omit<{ id: number; op: "style"; style: number; target: BridgeTarget; path: string[]; format: TextStyle; }, "id"> | Omit<{ id: number; op: "unstyle"; style: number; }, "id"> | Omit<{ id: number; op: "key"; key: number; spec: KeySpec; }, "id"> | Omit<{ id: number; op: "unkey"; key: number; }, "id"> | Omit<{ id: number; op: "note"; table: number; verb: "write"; target: BridgeTarget; path: string[]; value: BridgeArgument; }, "id"> | Omit<{ id: number; op: "note"; table: number; verb: "read" | "has" | "forget"; target: BridgeTarget; path: string[]; }, "id"> | Omit<{ id: number; op: "note"; table: number; verb: "keys" | "clear"; }, "id"> | Omit<{ id: number; op: "find"; target: BridgeTarget; path: string[]; spec: FindSpec; }, "id"> | Omit<{ id: number; op: "drive"; drive: number; spec: DriveSpec; }, "id"> | Omit<{ id: number; op: "undrive"; drive: number; }, "id"> | Omit<{ id: number; op: "track"; track: number; target: BridgeTarget; path: string[]; className: string; mode: TrackMode; everyFrames: number; }, "id"> | Omit<{ id: number; op: "untrack"; track: number; }, "id"> | Omit<{ id: number; op: "collect"; target: BridgeTarget; path: string[]; spec: CollectSpec; }, "id"> | Omit<{ id: number; op: "when"; condition: HookCondition; everyFrames?: number | undefined; timeoutMs?: number | undefined; reads?: WatchEntry[] | undefined; handles?: boolean | undefined; }, "id"> | Omit<{ id: number; op: "chain"; steps: ChainStep[]; }, "id"> | Omit<{ id: number; op: "snapshot"; target: BridgeTarget; path: string[]; scale: number; background: number | null; maxSize: number; }, "id"> | Omit<{ id: number; op: "answer"; callback: number; value?: BridgeArgument | undefined; }, "id"> | Omit<{ id: number; op: "dispose"; from: number; to: number; }, "id"> | Makes the request that starts it, given its number. |
stop | (number: number) => Omit<{ id: number; op: "get"; target: BridgeTarget; path: string[]; }, "id"> | Omit<{ id: number; op: "set"; target: BridgeTarget; path: string[]; value: BridgeArgument; }, "id"> | Omit<{ id: number; op: "call"; target: BridgeTarget; path: string[]; args: BridgeArgument[]; }, "id"> | Omit<{ id: number; op: "construct"; className: string; args: BridgeArgument[]; }, "id"> | Omit<{ id: number; op: "describe"; target: BridgeTarget; path: string[]; }, "id"> | Omit<{ id: number; op: "listen"; target: BridgeTarget; path: string[]; event: string; listener: number; read?: string[][] | undefined; capture?: boolean | undefined; }, "id"> | Omit<{ id: number; op: "unlisten"; listener: number; }, "id"> | Omit<{ id: number; op: "release"; handles: number[]; }, "id"> | Omit<{ id: number; op: "batch"; requests: BridgeRequest[]; }, "id"> | Omit<{ id: number; op: "hookTable"; url?: string | undefined; }, "id"> | Omit<{ id: number; op: "hook"; registration: number; hook: number; rules: HookRules; }, "id"> | Omit<{ id: number; op: "rehook"; registration: number; rules: HookRules; resetCounts: boolean; }, "id"> | Omit<{ id: number; op: "unhook"; registration: number; }, "id"> | Omit<{ id: number; op: "hookStats"; }, "id"> | Omit<{ id: number; op: "watch"; watch: number; entries: WatchEntry[]; everyFrames: number; handles: boolean; }, "id"> | Omit<{ id: number; op: "unwatch"; watch: number; }, "id"> | Omit<{ id: number; op: "tick"; tick: number; everyFrames: number; }, "id"> | Omit<{ id: number; op: "untick"; tick: number; }, "id"> | Omit<{ id: number; op: "draw"; node: number; spec: OverlaySpec; }, "id"> | Omit<{ id: number; op: "erase"; nodes: number[]; }, "id"> | Omit<{ id: number; op: "measure"; text?: string | undefined; spans?: TextSpan[] | null | undefined; font?: OverlayFont | undefined; padding?: number | undefined; wrapWidth?: number | undefined; }, "id"> | Omit<{ id: number; op: "bounds"; node: number; }, "id"> | Omit<{ id: number; op: "highlight"; highlight: number; target: BridgeTarget; path: string[]; effect: HighlightEffect; }, "id"> | Omit<{ id: number; op: "unhighlight"; highlight: number; }, "id"> | Omit<{ id: number; op: "render"; render: number; spec: RenderSpec; }, "id"> | Omit<{ id: number; op: "unrender"; render: number; }, "id"> | Omit<{ id: number; op: "renderStats"; render: number; }, "id"> | Omit<{ id: number; op: "shader"; shader: number; spec: ShaderPassSpec; }, "id"> | Omit<{ id: number; op: "unshader"; shader: number; }, "id"> | Omit<{ id: number; op: "shaderStats"; shader: number; }, "id"> | Omit<{ id: number; op: "invokeOriginal"; registration: number; token: number; arguments: BridgeArgument[] | null; }, "id"> | Omit<{ id: number; op: "overrideDone"; registration: number; token: number; }, "id"> | Omit<{ id: number; op: "style"; style: number; target: BridgeTarget; path: string[]; format: TextStyle; }, "id"> | Omit<{ id: number; op: "unstyle"; style: number; }, "id"> | Omit<{ id: number; op: "key"; key: number; spec: KeySpec; }, "id"> | Omit<{ id: number; op: "unkey"; key: number; }, "id"> | Omit<{ id: number; op: "note"; table: number; verb: "write"; target: BridgeTarget; path: string[]; value: BridgeArgument; }, "id"> | Omit<{ id: number; op: "note"; table: number; verb: "read" | "has" | "forget"; target: BridgeTarget; path: string[]; }, "id"> | Omit<{ id: number; op: "note"; table: number; verb: "keys" | "clear"; }, "id"> | Omit<{ id: number; op: "find"; target: BridgeTarget; path: string[]; spec: FindSpec; }, "id"> | Omit<{ id: number; op: "drive"; drive: number; spec: DriveSpec; }, "id"> | Omit<{ id: number; op: "undrive"; drive: number; }, "id"> | Omit<{ id: number; op: "track"; track: number; target: BridgeTarget; path: string[]; className: string; mode: TrackMode; everyFrames: number; }, "id"> | Omit<{ id: number; op: "untrack"; track: number; }, "id"> | Omit<{ id: number; op: "collect"; target: BridgeTarget; path: string[]; spec: CollectSpec; }, "id"> | Omit<{ id: number; op: "when"; condition: HookCondition; everyFrames?: number | undefined; timeoutMs?: number | undefined; reads?: WatchEntry[] | undefined; handles?: boolean | undefined; }, "id"> | Omit<{ id: number; op: "chain"; steps: ChainStep[]; }, "id"> | Omit<{ id: number; op: "snapshot"; target: BridgeTarget; path: string[]; scale: number; background: number | null; maxSize: number; }, "id"> | Omit<{ id: number; op: "answer"; callback: number; value?: BridgeArgument | undefined; }, "id"> | Omit<{ id: number; op: "dispose"; from: number; to: number; }, "id"> | Makes the request that ends it, given the same number. |
listener | (message: SubscriptionMessage) => void | Called with each message Flash posts under the number, in order. |
Returns
Promise<Unsubscribe>. A function that stops it.
Types
Example
const stop = await phantom.flash.bridge.subscribe(
(tick) => ({ op: 'tick', tick, everyFrames: 24 }),
(tick) => ({ op: 'untick', tick }),
(message) => { if (message.kind === 'tick') console.log('frame', message.frame); }
);tick
tick(everyFrames: number, listener: (frame: number, time: number) => void) => Promise<Unsubscribe>Hears every everyFramesth frame. Listeners at the same rate share one subscription in Flash, which ends with the last of them.
The raw form of phantom.flash.onTick, for doing something in step with the game. The rate is rounded down and at least 1. A tick is a message across the bridge, so hear only as often as you need to: at 24 frames a second, everyFrames: 24 is about once a second.
Parameters
| Name | Type | |
|---|---|---|
everyFrames | number | How many frames between calls: 1 for every frame. |
listener | (frame: number, time: number) => void | Called with the loader's frame count and Flash's clock (getTimer, in milliseconds) on each tick. |
Returns
Promise<Unsubscribe>. A function that stops this listener; the subscription in Flash ends with the rate's last listener.
Types
Example
const stop = await phantom.flash.bridge.tick(24, (frame, time) => {
console.log(`Frame ${frame}, ${(time / 1000).toFixed(1)}s since Flash started`);
});track
track(entry: WatchEntry, className: string, { mode, everyFrames }: { mode?: TrackMode | undefined; everyFrames?: number | undefined; } | undefined, listener: (change: { added: FlashValue[]; removed: FlashValue[]; }) => void) => Promise<Unsubscribe>A live list of every instance of a class, kept current in the game. Resolves with what ends it.
The raw form of phantom.flash.track. The game looks every few frames and tells you only what the list gained and lost, so following every avatar in a room costs nothing while nothing changes. displayList walks what is drawn under entry; holder reads the collection at entry instead, for the game's own lists of things that are never drawn. A subclass counts as its class, and so does anything implementing an interface. Only a loader that lists the track feature keeps lists.
Parameters
| Name | Type | |
|---|---|---|
entry | WatchEntry | Where to look, as FlashReference.entry gives it. |
className | string | The class or interface to count, by qualified name. |
__2 optional | { mode?: TrackMode | undefined; everyFrames?: number | undefined; } | mode, where to look; everyFrames, how often the game looks. |
listener | (change: { added: FlashValue[]; removed: FlashValue[]; }) => void | Called with the handles the list gained and lost. |
Returns
Promise<Unsubscribe>. A function that stops the list.
Types
Example
const stop = await phantom.flash.bridge.track(
phantom.flash.bridge.stage().entry,
'avatar.AvatarWorldView',
{ mode: 'displayList', everyFrames: 15 },
({ added, removed }) => console.log('+', added.length, '-', removed.length)
);vector
vector(itemClassName: string, items?: readonly FlashArgument[]) => Promise<FlashHandle>Makes a Vector.<Item> holding those items, and resolves with a handle to it.
Reach for this wherever the client declares a Vector.<T>, which the generated declarations in @phantom/client-types write as Vector<T>. An Array will not do in its place: a parameterised Vector is its own class in AVM2, and an Array coerced to one throws TypeError #1034 in the client before the method body runs. That is the whole of what went wrong with a den shop, where requestStoreUpdateItems takes three Vector.<MyShopItem> and had been handed three arrays.
The class is fetched from the client's own application domain by the name the runtime knows it under, and the vector is filled a push at a time, since the constructor takes a length rather than contents. It fails with FLASH when the client has no such class, which for a Vector means nothing in the client ever named that one.
Parameters
| Name | Type | |
|---|---|---|
itemClassName | string | The class of what it holds, dotted: shop.MyShopItem, String, *. |
items optional | readonly FlashArgument[] | The items to push, in order. A handle goes in as its object. |
Returns
Promise<FlashHandle>. A handle to the new vector, to release when it is no longer needed.
Types
Example
const item = await phantom.flash.bridge.construct('shop.MyShopItem');
const added = await phantom.flash.bridge.vector('shop.MyShopItem', [item]);
const removed = await phantom.flash.bridge.vector('shop.MyShopItem');
const repriced = await phantom.flash.bridge.vector('shop.MyShopItem');
await phantom.flash.bridge
.definition('shop.ShopToSellXtCommManager')
.at('requestStoreUpdateItems')
.call(4, 'state-uuid', added, removed, repriced, () => undefined, null);
await phantom.flash.bridge.release([item, added, removed, repriced]);watch
watch(entries: readonly WatchEntry[], options: { everyFrames?: number | undefined; handles?: boolean | undefined; }, listener: (values: FlashValue[], errors: (string | null)[] | null) => void) => Promise<Unsubscribe>Reads entries every everyFrames frames in Flash, and hears their values when one changed.
The raw form of phantom.flash.watch. All the entries are read together in Flash, and the listener is told only when a value differs from the last read, so a watch costs nothing across the bridge while nothing changes. A read that fails (a null along the path) gives null, with the reason in errors. Objects cross as { $object: className } unless handles is set.
Parameters
| Name | Type | |
|---|---|---|
entries | readonly WatchEntry[] | The places to read, as FlashReference.entry gives them. |
options | { everyFrames?: number | undefined; handles?: boolean | undefined; } | everyFrames: read every this many frames (1, every frame, by default; at least 1). handles: objects as handles, each then held in Flash until released. |
listener | (values: FlashValue[], errors: (string | null)[] | null) => void | Called with every value, in the order of entries, whenever one changed; and with the reason each read failed, or null when none did. |
Returns
Promise<Unsubscribe>. A function that ends the watch.
Types
Example
const view = phantom.flash.bridge.definition('avatar.AvatarManager').at('playerAvatarWorldView');
const stop = await phantom.flash.bridge.watch([view.at('x').entry, view.at('y').entry], { everyFrames: 6 },
([x, y]) => console.log('Now at', x, y));when
when(condition: HookCondition, options?: { everyFrames?: number | undefined; timeoutMs?: number | undefined; reads?: readonly WatchEntry[] | undefined; handles?: boolean | undefined; }) => Promise<WhenResult>Waits in the game until a condition holds, and answers what it read in the frame it held.
The raw form of phantom.flash.until, which is what to use. Nearly everything in the client is null until the client builds it: the HUD before play starts, a window before the player opens it, a list before the server answers. Looking again and again is a message each way per look, and a watch posts every change until one of them is the change wanted. This waits in Flash instead: the condition is read on the frames it names and one message comes back, when it holds or when the time is up. A condition that already holds is answered without waiting a frame.
The condition is read between calls, so it reads constants, a class's values and a place's, as a drive's while does; exists on a place is the common one. reads are read in the frame it held, which is the only frame their values are the ones it held for, so a wait for an animal to arrive answers where it arrived without a second message.
Running out of time is an answer, not a failure: met is false and values is empty. Only a loader that lists the when feature can be asked.
Parameters
| Name | Type | |
|---|---|---|
condition | HookCondition | What is waited for. See HookCondition. |
options optional | { everyFrames?: number | undefined; timeoutMs?: number | undefined; reads?: readonly WatchEntry[] | undefined; handles?: boolean | undefined; } | everyFrames, how often the game reads it; timeoutMs, how long to wait, ten seconds by default and ten minutes at the most; reads, places read in the frame it held; handles, to be handed those values as handles. |
Returns
Promise<WhenResult>. Whether it held, how long it took in frames and milliseconds, and what it read.
Example
const hud = phantom.flash.bridge.definition('gui.GuiManager').at('mainHud');
const waited = await phantom.flash.bridge.when(
{ test: 'exists', left: { from: 'place', target: hud.target, path: [...hud.path] } },
{ timeoutMs: 30_000, reads: [hud.at('currentFrameLabel').entry] }
);
console.log(waited.met ? `the HUD came up on ${waited.values[0]}` : 'no HUD in half a minute');whenReady
whenReady() => Promise<void>Resolves once the loader has said it is running.
Resolves at once when the bridge is already ready, and rejects with a BridgeError: CLOSED when the bridge is or becomes closed, VERSION when the loader speaks another bridge version. It waits for as long as it takes; requests made before then wait too, but only as long as their timeout. The loader being ready does not mean the game client has loaded (onClient).
Returns
Promise<void>
Example
await phantom.flash.bridge.whenReady();
console.log('Loader features:', phantom.flash.bridge.features.join(', '));onClient
onClient(listener: (loaded: boolean, message: string | null) => void) => () => voidHears whether the game client loaded, or why not.
The loader reports it once, when the client it loads is on the stage or has failed to load. It is not repeated for a listener added later, so a script started once the game is running does not hear it: check state and read the client instead. Listeners are dropped when the bridge closes.
Parameters
| Name | Type | |
|---|---|---|
listener | (loaded: boolean, message: string | null) => void | Called with true and null once the client is on the stage, or with false and the loader's reason when it could not be loaded. |
Returns
() => void. A function that stops listening.
Example
const stop = phantom.flash.bridge.onClient((loaded, message) => {
if (loaded) console.log('Animal Jam is on the stage');
else console.error('The client did not load:', message);
});onKey
onKey(spec: KeySpec, listener: (message: { kind: "key"; key: number; code: number; char: string; ctrl: boolean; shift: boolean; alt: boolean; phase: "down" | "up"; consumed?: boolean | undefined; }) => void) => Promise<Unsubscribe>Hears a key at the stage.
The raw form of phantom.flash.onKey, which reads a shortcut such as Ctrl+Shift+H into the spec. Keys are heard where Flash sees them, at the stage, so only while the game has focus.
Parameters
| Name | Type | |
|---|---|---|
spec | KeySpec | Which key and modifiers, on key down or up, whether to keep it from the game, what a focused text field does to it (whileTyping) and whether to hear its repeats. See KeySpec. |
listener | (message: { kind: "key"; key: number; code: number; char: string; ctrl: boolean; shift: boolean; alt: boolean; phase: "down" | "up"; consumed?: boolean | undefined; }) => void | Called with each matching press: its key code, character, modifiers and phase. |
Returns
Promise<Unsubscribe>. A function that stops listening.
Types
Example
const stop = await phantom.flash.bridge.onKey(
{ code: 72, ctrl: true, shift: false, alt: false, phase: 'down', consume: true, whileTyping: 'empty', repeat: false },
(press) => console.log('Ctrl+H, char', press.char)
);onReloadLoss
onReloadLoss(listener: (loss: ReloadLoss) => void) => () => voidHears what the bridge could not put back after the game's page loaded the client again.
The bridge asks the new loader for everything it had started in the old one, and most things go back without a caller knowing anything happened: the number changes underneath, the stop still works and the listener keeps firing. Two kinds do not. One aimed at an object held by handle cannot, because the object died with the loader that held it, and a drive that writes once has already written. Each of those, and each re-arm the loader refused, is reported here.
With no listener anywhere, the bridge warns on the console once per reload instead, because a subscription that stops posting with nothing said is the whole reason this went unnoticed.
Parameters
| Name | Type | |
|---|---|---|
listener | (loss: ReloadLoss) => void | Called once for each thing that could not be started again. |
Returns
() => void. A function that stops listening.
Example
phantom.flash.bridge.onReloadLoss(({ op, reason }) => {
console.warn(`that ${op} is gone for good: ${reason}`);
});onSubscription
onSubscription(number: number, listener: (message: SubscriptionMessage) => void) => () => voidHears a message a subscription made some other way posts (an overlay node's), until the function it gives back is called.
For things numbered by allocate and started by a request of their own, as overlay nodes are by draw: their mouse and widget events arrive under the node's number. A number has one listener; a second replaces the first. erase drops the listener of the nodes it erases.
Parameters
| Name | Type | |
|---|---|---|
number | number | The number the messages are posted under, such as an overlay node's. |
listener | (message: SubscriptionMessage) => void | Called with each message posted under number. |
Returns
() => void. A function that stops listening, if the listener is still the number's.
Example
const bridge = phantom.flash.bridge;
const node = bridge.allocate();
await bridge.draw(node, { type: 'button', x: 20, y: 60, width: 120, height: 30, label: 'Wave' });
const stop = bridge.onSubscription(node, (message) => {
if (message.kind === 'overlay' && message.event === 'click') console.log('Waving!');
});onUncaught
onUncaught(listener: (error: BridgeError) => void) => () => voidHears errors the game client threw and nothing caught.
The loader reports errors that reach Flash's uncaught error handler, errors thrown by its own work each frame (hooks, watches, ticks, the overlay, highlights, styles), a hook table that failed to load (named PhantomHooks), and smooth motion stopping itself (named PhantomMotion). The game carries on after each. Listeners are dropped when the bridge closes.
Parameters
| Name | Type | |
|---|---|---|
listener | (error: BridgeError) => void | Called with a BridgeError of code FLASH for each error, whose flash holds Flash's own name, message and error id. |
Returns
() => void. A function that stops listening.
Example
const stop = phantom.flash.bridge.onUncaught((error) => {
console.warn('The game threw', error.flash?.name, error.message);
});Types
The types the calls above take and give, with every field. Import any of them in a plugin from @phantomclient/plugin/sdk, or use them as they are in a script.
FlashPlaceLike
Somewhere in Flash, however a script holds it: a typed place (avatars.playerAvatarWorldView), an object read before (held by handle), or an untyped reference.
type FlashPlaceLike = { readonly $reference: FlashReference } | FlashReference | FlashHandleHighlightOptions
Highlights and colour changes, as scripts describe them, made into the loader's HighlightEffect.
const off = await phantom.flash.highlight(view, { color: 0x8b5cf6, strength: 2, pulse: true });
const normal = await phantom.flash.recolor({ saturation: 0 });| Field | Type | |
|---|---|---|
color optional | number | undefined | The glow's colour. A glow is drawn unless glow is false or another effect is asked for alone. |
strength optional | number | undefined | How strong the glow is: 2 by default. |
blur optional | number | undefined | How far the glow spreads, in pixels: 8 by default. |
alpha optional | number | undefined | |
inner optional | boolean | undefined | Glow inside the object's edges rather than around them. |
knockout optional | boolean | undefined | Only the glow, without the object. |
glow optional | boolean | undefined | |
pulse optional | boolean | { frames?: number | undefined; from?: number | undefined; to?: number | undefined; } | undefined | Fade in and out: true for a gentle pulse (from 60% to full strength and back every two and a half seconds), or how: frames for one cycle at the game's 24 a second, from and to as fractions of the glow. |
recolor optional | RecolorOptions | undefined | A colour change on the object, as recolor takes it. |
hide optional | boolean | undefined | Hide the object while highlighted. |
Unsubscribe
Ends a subscription in Flash. Calling it again does nothing.
type Unsubscribe = () => Promise<void>RecolorOptions
A colour change: a named look, a ColorMatrixFilter's twenty numbers, adjustments, a tint, or a ColorTransform's multipliers and offsets.
type RecolorOptions = | 'grayscale'
| 'sepia'
| 'invert'
| 'night'
| { matrix: number[] }
| {
/** -1 (black) to 1 (white); 0 leaves it. */
brightness?: number;
/** -1 to 1; 0 leaves it. */
contrast?: number;
/** 0 is grey, 1 leaves it, above 1 is more colourful. */
saturation?: number;
/** Degrees around the colour wheel. */
hue?: number;
}
| { tint: number; amount?: number }
| { transform: Partial<ColorTransformEffect> }KeyPress
A key as the listener hears it.
| Field | Type | |
|---|---|---|
code | number | |
char | string | The character typed, when there is one. |
ctrl | boolean | |
shift | boolean | |
alt | boolean | |
phase | "down" | "up" | |
consumed | boolean | Whether the loader kept the key from the game, and out of the chat bar. False for a key bound without consume, and for a loader from before it said so. |
KeyOptions
Keyboard shortcuts, by name: 'H', 'F2', 'Ctrl+Shift+H', 'Escape', or a key code. Codes are Flash's KeyboardEvent.keyCode, which for these keys are the browser's.
| Field | Type | |
|---|---|---|
phase optional | "down" | "up" | undefined | When the key goes down (the default) or up. |
consume optional | boolean | undefined | Keep the key from the game. By default the game hears it as ever. |
whileTyping optional | boolean | "empty" | "always" | "never" | undefined | When a text field having the keyboard stops the key being heard. 'empty', the default, hears it unless there is something typed in that field: Animal Jam gives the chat bar the keyboard as a room loads and takes it back after every message, so it holds it for nearly the whole of a session, and a key heard only with nothing focused is never heard at all. 'never' (also false) is the strict rule: nothing while a text field has the keyboard, empty or not. 'always' (also true) hears it whatever has the keyboard. |
repeat optional | boolean | undefined | Also for the repeats of a key held down. Off by default. |
yieldToTyping optional | boolean | undefined | Give way to typing: also quiet, and so not consumed, while the player has clicked into the focused field, the empty chat bar included, until they leave it with Escape, Enter or a click elsewhere. A press of it held when typing begins is let go (its release is heard) and its repeats are kept out of the field. Decided in Flash at the press. Off by default; for a key that walks, such as W. |
UntilOptions
How until waits.
| Field | Type | |
|---|---|---|
everyFrames optional | number | undefined | Read every this many frames. Every other frame by default. |
timeoutMs optional | number | undefined | Give up after this long and answer null. Ten seconds by default; Infinity waits for as long as the game is open. A wait longer than ten minutes, Infinity among them, is watched from here rather than waited for in the game, because that is as long as the game will hold a wait of its own. |
signal optional | AbortSignal | undefined | Gives the wait up when this is aborted, answering null as a wait that ran out of time does. It is what a supervised automation's task.signal is for: a run told to stop should not sit out the rest of a thirty second wait for a window the player has already closed. Answering null rather than throwing is deliberate, so a cancelled run unwinds through the same if (!found) return the timeout already goes through, with nothing to catch. What it costs depends on how the wait is being done. A watch is ended in the game at once (unwatch), so a cancelled wait leaves nothing running. A wait the game holds (when) can only be let go of here: the protocol has unwatch, untick, undrive and untrack and no unwhen, so the game reads its condition on until it holds or its own timeoutMs runs out, and the answer, whenever it comes, is dropped. That is one request left pending in the host and a condition read every everyFrames for up to timeoutMs, which is why a cancelled run is cheap to abandon but not free. |
WatchedValue
What watching a place reads: its declared type, for a typed place.
type WatchedValue<Place> = Place extends { $get(): Promise<infer Value> }
? Value
: FlashDataHookWhen
When a hook's rules apply: a condition, or as a shorthand, arguments by name and the values they must equal ({ userName: 'sxip' }).
type HookWhen = HookTest | { [argument: string]: unknown }HookTest
A condition. Made by phantom.flash.rules.
| Field | Type | |
|---|---|---|
__@TEST@169973 | RawTest |
RawTest
type RawTest = | { test: HookComparison; left: unknown; right?: unknown }
| { all: HookWhen[] }
| { any: HookWhen[] }
| { not: HookWhen }Waited
What a wait in the game answers: whether it held, how long it took, and what it read.
| Field | Type | |
|---|---|---|
met | boolean | Whether the condition came to hold. False for a wait that ran out of time or was given up on. |
frames | number | Frames the game drew while it waited. |
ms | number | Milliseconds by the game's own clock, so a frozen game's wait is measured in its own time. |
values | FlashData[] | One value per place asked for, in the order asked, read in the frame the condition held. Empty for a wait that did not hold: there was no such frame to read in. |
FlashData
Plain data read from Flash, with objects that live there as typed places.
type FlashData = | null
| undefined
| boolean
| number
| string
| FlashData[]
| { [key: string]: FlashData }
| TypedFlash<unknown>FlashPlace
The operations every place has, whatever its type.
| Field | Type | |
|---|---|---|
$get | () => Promise<FlashRead<T>> | Reads the value here: plain data as it is, an object as a typed place held by handle. |
$set | (value: FlashWrite<T>) => Promise<void> | Writes the value here, once. The client may put its own back a frame later: a member it sets every frame, or on every redraw, or an object it builds again for the next room. For a change that has to stay, use $keep. |
$keep | (value: FlashWrite<T>, options?: HoldOptions | undefined) => Promise<() => Promise<void>> | Writes the value here and keeps it here, until the function it gives back is called, which puts back what was there. It is $set for a change that has to last. The game writes it itself, every frame, so a value the client puts back is overwritten before Flash draws it. The place is followed rather than pinned: nothing is lost when the object is not there yet (it is written as soon as it is) or when the client builds it again (the new one is written too). It is phantom.flash.hold on this place, and takes the same options. ts const hud = phantom.flash.use('gui.GuiManager').mainHud; const faded = await hud.alpha.$keep(0.5); |
$call | (...args: FlashArgumentLike[]) => Promise<FlashData> | Calls what is here, whatever the declarations say is at this place. A place is callable in its own right (avatars.createAvatar(id, x, y, name)), which is how to write a call the declarations describe: the arguments and the answer are both checked. This is the same call for the places they cannot describe, and it saves casting one to reach it: - a class whose instance side declares nothing, because every member of it is static (gui.EmoticonUtility), so $as and a hook's self give a place with no members; - an object the client typed as Object or *, or one reached with $at; - a member holding a function, such as a callback the client keeps in a field. It answers FlashData, since there is no declared return to read it as. ts const emotes = phantom.flash.use('gui.EmoticonUtility'); console.log(await emotes.stringForId(3)); console.log(await emotes.$at('stringForId').$call(3)); |
$listen | (event: string, listener: (event: FlashData, keep: <Kept>(value: Kept) => Kept) => void, options?: { read?: readonly (readonly string[])[] | undefined; capture?: boolean | undefined; } | undefined) => Promise<() => Promise<void>> | Listens for an event on the object here. Resolves with what stops listening. Without read, the event is its readable members, objects among them as typed places held by handle, and every one of them is released once the listener returns, except those passed to keep before then (release those with $release when done). read names the paths to read as the event is dispatched instead: the event is then { className, type, read } with the values in that order, and nothing is held. A listener that ignores the event passes read: []. capture listens on the way down. Both are FlashBridge.listen's options. ts await stage.$listen('mouseMove', (event) => console.log((event as { read: number[] }).read), { read: [['stageX'], ['stageY']], }); |
$describe | () => Promise<FlashValue> | The public members of what is here, as Flash's describeType lists them. |
$at | (...path: string[]) => AnyFlash | Further along, by names the types do not know: untyped from here on. |
$as | { <Name extends keyof ClientInstances & string>(name: Name): TypedFlash<ClientInstances[Name]>; (name: string): AnyFlash; } | The same place, read as the client's name from here on. Nothing is sent and nothing is checked in the game: it says what the object here is, so the editor completes and checks its members again after a step the declarations could not follow ($at, a hook's capture, anything the client typed as Object or *). Naming the wrong class is not caught here; the read or call that follows is what fails. ts const grid = phantom.flash .use('gui.AvatarEditor') .$at('_colorTableColor1') .$as('gui.ColorTable'); console.log(await grid._numCols.$get(), 'by', await grid._numRows.$get()); |
$release | () => Promise<void> | Releases the object this place is held by, when it is one; nothing otherwise. |
$reference | FlashReference | The untyped reference underneath. |
FlashRead
What reading a place of type T resolves with.
type FlashRead<T> = unknown extends T
? FlashData
: T extends Primitive
? T
: T extends readonly (infer Item)[]
? FlashRead<Item>[]
: T extends (...args: never[]) => unknown
? TypedFlash<unknown>
: TypedFlash<T>FlashWrite
What may be given where Flash expects T.
type FlashWrite<T> = unknown extends T
? FlashArgumentLike
: T extends Primitive
? T
: T extends readonly (infer Item)[]
? FlashWrite<Item>[]
: T extends (...args: never[]) => unknown
? ((...args: FlashData[]) => void) | FlashCallback
: TypedFlash<T> | FlashHandle | FlashArgumentLikeHoldOptions
How a hold behaves.
| Field | Type | |
|---|---|---|
everyFrames optional | number | undefined | Write on every nth frame. Every frame by default, which is what a value the client resets needs. |
when optional | "enter" | "exit" | undefined | Which half of the frame to write in. 'enter' by default, before the frame is read. Use 'exit' for a member the game writes every frame of its own, such as a thing it pins to the pointer. Two objects' enter-frame listeners have no order between them, so an ordinary hold on one of those stands only on the frames the loader happens to run second, and the value flickers or never takes at all. Exit-frame work runs after every enter-frame listener, so what is held is what is drawn and what the next frame reads. |
while optional | HookWhen | undefined | Only while this holds, in the same rules as a hook's when, read between calls. |
once optional | boolean | undefined | Write once, on the first frame the condition holds, then stop. |
restore optional | boolean | undefined | Put back what was there before the first write when the hold ends. On by default. |
onFailed optional | ((error: string) => void) | undefined | Called if the game turns the hold off because a write failed. |
FlashArgumentLike
Anything the bridge carries, typed places included.
type FlashArgumentLike = FlashArgument | TypedFlash<unknown> | FlashArgumentLike[] | { [key: string]: FlashArgumentLike }FlashValue
A value on the TypeScript side: plain data, undefined, or a handle to an object in Flash.
type FlashValue = | null
| undefined
| boolean
| number
| string
| FlashValue[]
| { [key: string]: FlashValue }
| FlashHandleFlashArgument
What may be passed to Flash: a value, a function Flash may call, or an object to make there.
type FlashArgument = | FlashValue
| FlashArgument[]
| { [key: string]: FlashArgument }
| ((...args: FlashValue[]) => void)
| FlashNewAnyFlash
A place whose class the declarations do not describe: phantom.flash.use('a.Class') for a class the client has no declaration for, $at, $as with a name, or anything read back from one.
Every member is another place of the same kind, and every place can be called, which is what reflection allows and what a place does at run time. Nothing is checked, because there is nothing to check it against: a name the game does not have fails when it is used, not here. then is left out, so awaiting a place is still the mistake it always was.
type AnyFlash = FlashPlace<unknown> &
FlashMethod<unknown[], unknown> & {
readonly [member: string]: AnyFlash;
} & { then?: undefined }FlashMethod
A method of the client, called with checked arguments.
type FlashMethod<Args extends readonly unknown[], Result> = (
...args: { [Index in keyof Args]: FlashWrite<Args[Index]> }
) => Promise<FlashRead<Result>>TypedFlash
A place in Flash whose declared type is T.
type TypedFlash<T> = FlashPlace<T> &
FlashConstructor<T> &
(unknown extends T
? unknown
: {
readonly [Key in keyof T as Key extends `$${string}` ? never : Key]: T[Key] extends (
...args: infer Args
) => infer Result
? FlashMethod<Args, Result>
: TypedFlash<T[Key]>;
})FlashConstructor
A class's constructor, from its declared $new.
type FlashConstructor<T> = T extends { $new(...args: infer Args): infer Instance }
? { $new: FlashMethod<Args, Instance> }
: unknownWheneverObject
The object attach is handed: the place's own type, held by handle.
type WheneverObject<Source> = Source extends { $get(): Promise<infer Value> }
? NonNullable<Value> extends FlashPlace<unknown>
? NonNullable<Value>
: AnyFlash
: AnyFlashWheneverOptions
How whenever looks.
| Field | Type | |
|---|---|---|
everyFrames optional | number | undefined | Look every this many frames. Every twelfth by default, which is about twice a second. |
moments optional | readonly HookName[] | undefined | Hooks to look at once on, as well: the client's own methods that build what is being kept. WHENEVER_MOMENTS by default, which are a room and the HUD being built. An empty list leaves only the looking every few frames. |
onGone optional | (() => void) | undefined | Called when the object has gone and nothing has taken its place. |
onFailed optional | ((error: unknown) => void) | undefined | Called with what attach or a detach threw. Logged to the console when left out. |
HookName
The names hooks are looked up by: every hook point Phantom puts into the client as it serves it, from the generated declarations, so every phantom.flash.hook call is checked against the hooks that exist.
type HookName = | (keyof ClientHooks & string)
// A getter or setter may also be named without its `@get`/`@set`, as the lookup allows.
| WithoutAccessor<Extract<keyof ClientHooks & string, `${string}@${'get' | 'set'}`>>
/**
* A class's constructor, as `gui.ColorTable.<constructor>`. A constructor is no member of the
* class, so the generated declarations have no name for it; the hooks patch gives it this one,
* which no ActionScript member can be called. The hook runs after the constructor's `super()`,
* since the object is not built until then, and skipping it returns from the constructor with
* the object half made.
*/
| `${string}.${typeof CONSTRUCTOR_MEMBER}`TickOptions
How often ticks are heard, and what becomes of a listener that fails.
| Field | Type | |
|---|---|---|
everyFrames optional | number | undefined | How many frames between ticks. Every frame by default. |
onFailed optional | ((error: unknown) => void) | undefined | Called with what the listener threw, or with what its promise rejected with. Logged when left out. A listener that fails is never dropped, and never stops the ticks of anything else listening at the same rate. |
CollectFields
Which members to read out of every item: a path each, under the name to hear it by.
type CollectFields = readonly string[] | Readonly<Record<string, string | string[]>>CollectOptions
How much of a list to read, which items, and in what order.
| Field | Type | |
|---|---|---|
where optional | HookWhen | undefined | Only the items this holds for, decided in the game before anything crosses. phantom.flash.rules.item(...) reads the item being tested, and the rest of the rules are there as usual: a constant, fromClass, another place, and all, any and not to combine them. So "the actors in this room that are drawn" is one message and the rows that matter, rather than every row and a filter here. rules.changed has no meaning in a where, since each item is tested once, and is refused. |
sortBy optional | CollectOrder | undefined | Put the items in this order before the answer is built, read once per item. With limit: 1 it answers the nearest, the cheapest or the newest without a list crossing at all. |
skip optional | number | undefined | Items to pass over first, for reading a long list a page at a time. Counted after where. |
limit optional | number | undefined | At most this many items. 2000 when left out, which is also the most the game answers. |
handles optional | boolean | undefined | Give objects back as places to read and call, and each item itself under $item. Otherwise an object is named rather than held, which costs nothing and keeps nothing alive. |
Collected
What a collect answers: a row per item read, and how many items there were.
| Field | Type | |
|---|---|---|
rows | Record<string, FlashData>[] | |
total | number | How many items a where left, which is what paging through the answer counts against. More than rows when skip or limit cut it short. |
scanned | number | How long the list itself was, before a where narrowed it. The same number as total for a collect with no where, so total of scanned says how much of the list was worth reading. |
ChainSteps
How a chain is described: the steps, or a function handed step to name them by.
type ChainSteps = readonly ChainStepSpec[] | ((step: (which: number) => ChainStepRef) => readonly ChainStepSpec[])ChainStepSpec
One step of a chain: the work it does, whether to hand its answer over, and what it needs first.
type ChainStepSpec = {
/**
* Read before the step runs, with `rules.item` reading what the step before it answered. Where
* it does not hold the chain stops there, and `stopped` says which step that was: "open the
* window, then press the button only if it is the right window" is one message.
*/
if?: HookWhen;
/**
* Hand this step's answer over as a place held by handle, as a `$get` of its own would. Without
* it an object is named by its class and stays in Flash, which is why a chain of ten steps
* leaves nothing to release.
*/
hold?: boolean;
} & (
| { get: ChainFrom }
| { set: ChainFrom; to: unknown }
| { call: ChainFrom; args?: readonly unknown[] }
/** A new object of one of the game's classes, by qualified name. */
| { make: string; args?: readonly unknown[] }
/** The same members off every item of a list the chain has reached. */
| { collect: ChainFrom; fields: CollectFields; options?: CollectOptions }
)ChainStepRef
What an earlier step answered, as a later step names it: step(-1) is the step before it, and step(0) the first step of the chain. Counting back is what a chain written without counting its own steps says, and it keeps reading right when a step is added above.
at goes further along from it, so step(-1).at('name') reads name off what the step before answered.
| Field | Type | |
|---|---|---|
$step | number | |
path | readonly string[] | |
at | (...path: (string | string[])[]) => ChainStepRef |
Chained
What a chain answers: each step's value, and the step an if stopped it at.
| Field | Type | |
|---|---|---|
values | FlashData[] | What each step answered, in the chain's own order. A step the chain never reached has no value here, so this is shorter than the chain when stopped is a number. |
stopped | number | null | The step an if stopped the chain at, or null where every step ran. |
collected | (step: number) => Collected | The rows a collect step read, by that step's number (a negative one counts back from the end). Throws for a step that is no collect, or one the chain did not reach. |
FoundPlaces
What a search of the client's own classes answers: places typed as that class's instances.
type FoundPlaces<Name> = Name extends keyof ClientInstances
? TypedFlash<ClientInstances[Name]>[]
: TypedFlash<unknown>[]InspectOptions
| Field | Type | |
|---|---|---|
depth optional | number | undefined | How many levels below the root to read. 4 by default, 16 at the most. |
bounds optional | boolean | "stage" | FlashPlaceLike | undefined | Measure where each object is drawn: true in the root's own space, 'stage' in the stage's, or in the space of a place given. Off by default, as it reads five more members of each. |
limit optional | number | undefined | The most objects to read; the walk stops there. 500 by default, 5,000 at the most. |
handles optional | boolean | undefined | Keep every node's handle (node.handle), to act on what was found. Release them after. |
Inspection
What inspect answers.
| Field | Type | |
|---|---|---|
root | InspectedNode | |
count | number | How many objects were read. |
truncated | boolean | Whether the walk stopped before the end, at depth or at limit. |
space | "root" | "stage" | "place" | null | The space the bounds are in: root, stage, or place for one given. |
outline | () => string | The tree as indented text, one object a line, for a log or a console. |
nodes | () => InspectedNode[] | Every node, the root first, depth first. |
find | (test: (node: InspectedNode) => boolean) => InspectedNode | null | The first node, depth first, that test holds for. |
release | () => Promise<void> | Lets go of the handles kept with handles: true. Nothing to do without. |
InspectedNode
One display object of an inspection. Anything that could not be read is null.
| Field | Type | |
|---|---|---|
name | string | null | Its name, as the game gave it or Flash did (instance12). |
className | string | Its class, dotted: flash.display.MovieClip, gui.GuiSoundButton. |
path | number[] | Child indexes from the root down to it: [] for the root, [2, 0] for the first child of its third. |
visible | boolean | null | |
alpha | number | null | |
x | number | null | |
y | number | null | |
scaleX | number | null | |
scaleY | number | null | |
rotation | number | null | |
width | number | null | Its size in its parent's space, as width and height give it. |
height | number | null | |
bounds | InspectedBounds | null | Where it is drawn, children and all, in the space asked for (bounds), to the pixel: from the stage's transform.pixelBounds, mapped into that space. Null where bounds were not asked for. |
frames | InspectedFrames | null | Its timeline, for a movie clip; null for anything else. |
text | string | null | A text field's text; null for anything else. |
mouseEnabled | boolean | null | |
mouseChildren | boolean | null | |
timeline | boolean | null | Whether its parent holds it as a member of its own name, which is what Flash does for every named object a timeline places: such an object cannot be renamed (Error #2078), and the timeline may put it back as it plays. False where the parent holds no such member, null where it could not be told (the root, an object named by Flash). |
childCount | number | How many children it has; 0 for an object that holds none. |
children | InspectedNode[] | Its children, read to the depth asked; fewer than childCount where the walk stopped. |
handle | FlashHandle | null | Held by handle, to act on, with handles: true; released by the inspection's release. |
InspectedBounds
A box, in the space the inspection was asked to measure in.
| Field | Type | |
|---|---|---|
x | number | |
y | number | |
width | number | |
height | number |
InspectedFrames
A movie clip's timeline, as it stands.
| Field | Type | |
|---|---|---|
current | number | |
total | number | |
label | string | null | The label at or before the current frame; null for none. |
labels | { name: string; frame: number; }[] | Every label of the clip's scene, in frame order. |
WatchOptions
| Field | Type | |
|---|---|---|
everyFrames optional | number | undefined | Read every this many frames: 1 (every frame) by default. |
handles optional | boolean | undefined | Objects as typed places held by handle, rather than { $object: className }. Each handle is counted once for each message it arrives in, and is yours to $release once per message you were told about. The loader used to take one on every read while only posting the reads that changed, so an object watched this way could never be let go of and was held in Flash for as long as the game ran. Watches.read now hands back what a quiet read took, so the count balances. Watching without it is still the lighter habit: watch for the change and read the place afterwards. Reach for this when the handle itself is what you are after. Note that a watch posts every entry's value in every message, so an unchanged entry's handle arrives again whenever a sibling changes, and that arrival is another one to release. Releasing a handle for something the watch is still reading is allowed and simply mints a fresh one on the next read, which the watch then reports as a change. |
onFailed optional | ((error: unknown) => void) | undefined | Called with what the listener threw, or with what its promise rejected with. Logged when left out. Either way the watch carries on: a listener that fails never ends it, and never keeps the other watches from being heard. |
HookOptions
| Field | Type | |
|---|---|---|
phase optional | "none" | "enter" | "exit" | "both" | undefined | What the listener hears: the call starting, returning, both, or nothing (rules only). By default enter when there is a listener, and nothing when there is not. |
capture optional | { args?: true | (string | number)[] | Record<string, string | (string | number)[]> | undefined; self?: string[] | Record<string, string | string[]> | undefined; value?: boolean | undefined; handles?: boolean | undefined; } | undefined | |
when optional | HookWhen | undefined | |
first optional | number | undefined | Only the first this many matching calls. |
every optional | number | undefined | Only every this many matching calls. |
skip optional | boolean | { returns: unknown; } | undefined | Skip the method: true returns nothing, { returns } returns a value. |
replaceArgs optional | Record<string, unknown> | undefined | Arguments to replace, by name or index. |
replaceReturn optional | unknown | The value to return instead. |
perFrame optional | number | undefined | Calls told to the listener in one frame at most (20 by default, 200 at most); the rest are counted. |
onDropped optional | ((dropped: number) => void) | undefined | Told how many calls went untold in a frame, over perFrame. Without it the first frame that drops any is warned about on the console, once, so a hook that only half reports says so. |
onError optional | ((error: BridgeError) => void) | undefined | Told when the rules threw inside the game: the hook is off from then on. |
HookCall
One hooked call, as the listener hears it.
| Field | Type | |
|---|---|---|
phase | "enter" | "exit" | |
call | number | Numbers the hook's calls, so an exit can be matched with its enter. |
args | Record<string, FlashData> | The captured arguments, by parameter name. |
argList | FlashData[] | The captured arguments, in the order asked for. |
self | Record<string, FlashData> | The captured paths on the object, by the names asked for. |
value optional | FlashData | The returned value, when captured, at exit. |
skipped | boolean | The call was skipped by a rule, and value is what it returned. |
hook | HookEntry | The hook, as the table describes it. |
HookEntry
One of the client's methods, getters or setters Phantom's rewrite of the client can call hooks for, as the loader reads it from the hook table. The table's own file is read in one place (HookTable.as); this is the shape the loader gives it in.
| Field | Type | |
|---|---|---|
id | number | The hook's number, as the rewritten client passes it to Hooks.enter. |
className | string | The class's qualified name with dots: avatar.AvatarManager. |
member | string | |
kind | "method" | "getter" | "setter" | |
isStatic | boolean | |
namespace | string | null | The member's namespace, when it is not public: phantom:private:avatar.AvatarManager. |
parameters | { name: string; type: string; optional: boolean; }[] | |
returns | string | |
rest optional | "rest" | "arguments" | null | undefined | A member taking ...rest (rest) or using arguments (arguments) is given that array as one more argument after the declared ones. Null or left out for the others. |
HookStop
Ends a hook. Also changes its rules, or reads its counts.
type HookStop = (() => Promise<void>) & {
readonly registration: number;
readonly hook: HookEntry;
/** New options for the same registration, keeping its listener (and its counts unless asked). */
update(options: HookOptions, update?: { resetCounts?: boolean }): Promise<void>;
/** How many calls it saw, matched and dropped. */
stats(): Promise<HookStats | null>;
}HookStats
| Field | Type | |
|---|---|---|
registration | number | |
hook | number | |
calls | number | Calls of the hook while the registration was on. |
matched | number | Of those, the ones its when matched. |
dropped | number |
OverrideHandler
type OverrideHandler<S = Signature> = (call: OverrideCall<S>) => unknownHookSignature
What ClientHooks declares for a hook's name, a getter or setter named plainly included.
type HookSignature<Name extends string> = Name extends keyof ClientHooks
? ClientHooks[Name]
: `${Name}@get` extends keyof ClientHooks
? ClientHooks[`${Name}@get`]
: `${Name}@set` extends keyof ClientHooks
? ClientHooks[`${Name}@set`]
: // A constructor returns nothing, so an override of one needs no `returns`.
Name extends `${string}.<constructor>`
? { self: unknown; args: unknown[]; returns: void }
: SignatureOverrideCall
One call the override caught, as its handler gets it.
| Field | Type | |
|---|---|---|
hook | HookEntry | The hook, as the table describes it. |
call | number | Numbers the hook's calls, as a hook listener hears them. |
self | TypedFlash<SelfOf<S>> | The object the method runs on, held by handle until the handler finishes; for a static member, its class. |
args | OverrideArgs | The arguments by parameter name, objects as places held until the handler finishes. An object, so call.args.userName reads one and call.argList is what destructures in order. |
argList | ReadArgs<ArgsOf<S>> | The arguments in order. |
arg | (nameOrIndex: string | number) => FlashData | One argument, by the name the client's source gives it or by its position, checked against the method's own parameters: an argument it does not have throws, naming the ones it does. args and argList are the same values unchecked. |
original | { (): Promise<FlashRead<ReturnsOf<S>>>; (...args: WriteArgs<ArgsOf<S>>): Promise<FlashRead<ReturnsOf<S>>>; } | Runs the client's own method for this call: with the call's arguments when none are given, or with these (all of them, in order). Resolves with what it returned. May be called more than once, and not after the handler has finished. |
OverrideArgs
The call's arguments under the names the client's own source gives its parameters, as call.args. An object, never a list: taking them in order is call.argList.
The declarations carry each argument's type but not its name (ClientHooks types them as a tuple), so this stays a record of FlashData and the names are checked in the game rather than in the editor. call.arg(name) is the checked way in: it throws naming the method's own parameters when there is no such argument, where a misspelt key here is quietly undefined.
type OverrideArgs = Record<string, FlashData> & {
/**
* Never here: it makes `const [a, b] = call.args` a mistake the editor reports, rather than one
* that reads correctly and fails at run time. `call.argList` is the list.
*/
readonly [Symbol.iterator]?: never;
}ReadArgs
| Field | Type | |
|---|---|---|
length | number | Gets or sets the length of the array. This is a number one higher than the highest index in the array. |
toString | () => string | Returns a string representation of an array. |
toLocaleString | { (): string; (locales: string | string[], options?: (NumberFormatOptions & DateTimeFormatOptions) | undefined): string; } | Returns a string representation of an array. The elements are converted to string using their toLocaleString methods. |
pop | () => FlashData | Removes the last element from an array and returns it. If the array is empty, undefined is returned and the array is not modified. |
push | (...items: FlashData[]) => number | Appends new elements to the end of an array, and returns the new length of the array. |
concat | { (...items: ConcatArray<FlashData>[]): FlashData[]; (...items: (FlashData | ConcatArray<FlashData>)[]): FlashData[]; } | Combines two or more arrays. This method returns a new array without modifying any existing arrays. |
join | (separator?: string | undefined) => string | Adds all the elements of an array into a string, separated by the specified separator string. |
reverse | () => FlashData[] | Reverses the elements in an array in place. This method mutates the array and returns a reference to the same array. |
shift | () => FlashData | Removes the first element from an array and returns it. If the array is empty, undefined is returned and the array is not modified. |
slice | (start?: number | undefined, end?: number | undefined) => FlashData[] | Returns a copy of a section of an array. For both start and end, a negative index can be used to indicate an offset from the end of the array. For example, -2 refers to the second to last element of the array. |
sort | (compareFn?: ((a: FlashData, b: FlashData) => number) | undefined) => FlashData[] | Sorts an array in place. This method mutates the array and returns a reference to the same array. |
splice | { (start: number, deleteCount?: number | undefined): FlashData[]; (start: number, deleteCount: number, ...items: FlashData[]): FlashData[]; } | Removes elements from an array and, if necessary, inserts new elements in their place, returning the deleted elements. |
unshift | (...items: FlashData[]) => number | Inserts new elements at the start of an array, and returns the new length of the array. |
indexOf | (searchElement: FlashData, fromIndex?: number | undefined) => number | Returns the index of the first occurrence of a value in an array, or -1 if it is not present. |
lastIndexOf | (searchElement: FlashData, fromIndex?: number | undefined) => number | Returns the index of the last occurrence of a specified value in an array, or -1 if it is not present. |
every | { <S extends FlashData>(predicate: (value: FlashData, index: number, array: FlashData[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: FlashData, index: number, array: FlashData[]) => unknown, thisArg?: any): boolean; } | Determines whether all the members of an array satisfy the specified test. |
some | (predicate: (value: FlashData, index: number, array: FlashData[]) => unknown, thisArg?: any) => boolean | Determines whether the specified callback function returns true for any element of an array. |
forEach | (callbackfn: (value: FlashData, index: number, array: FlashData[]) => void, thisArg?: any) => void | Performs the specified action for each element in an array. |
map | <U>(callbackfn: (value: FlashData, index: number, array: FlashData[]) => U, thisArg?: any) => U[] | Calls a defined callback function on each element of an array, and returns an array that contains the results. |
filter | { <S extends FlashData>(predicate: (value: FlashData, index: number, array: FlashData[]) => value is S, thisArg?: any): S[]; (predicate: (value: FlashData, index: number, array: FlashData[]) => unknown, thisArg?: any): FlashData[]; } | Returns the elements of an array that meet the condition specified in a callback function. |
reduce | { (callbackfn: (previousValue: FlashData, currentValue: FlashData, currentIndex: number, array: FlashData[]) => FlashData): FlashData; (callbackfn: (previousValue: FlashData, currentValue: FlashData, currentIndex: number, array: FlashData[]) => FlashData, initialValue: FlashData): FlashData; <U>(callbackfn: (previousValue: U, currentValue: FlashData, currentIndex: number, array: FlashData[]) => U, initialValue: U): U; } | Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function. |
reduceRight | { (callbackfn: (previousValue: FlashData, currentValue: FlashData, currentIndex: number, array: FlashData[]) => FlashData): FlashData; (callbackfn: (previousValue: FlashData, currentValue: FlashData, currentIndex: number, array: FlashData[]) => FlashData, initialValue: FlashData): FlashData; <U>(callbackfn: (previousValue: U, currentValue: FlashData, currentIndex: number, array: FlashData[]) => U, initialValue: U): U; } | Calls the specified callback function for all the elements in an array, in descending order. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function. |
find | { <S extends FlashData>(predicate: (value: FlashData, index: number, obj: FlashData[]) => value is S, thisArg?: any): S | undefined; (predicate: (value: FlashData, index: number, obj: FlashData[]) => unknown, thisArg?: any): FlashData; } | Returns the value of the first element in the array where predicate is true, and undefined otherwise. |
findIndex | (predicate: (value: FlashData, index: number, obj: FlashData[]) => unknown, thisArg?: any) => number | Returns the index of the first element in the array where predicate is true, and -1 otherwise. |
fill | (value: FlashData, start?: number | undefined, end?: number | undefined) => FlashData[] | Changes all array elements from start to end index to a static value and returns the modified array |
copyWithin | (target: number, start: number, end?: number | undefined) => FlashData[] | Returns the this object after copying a section of the array identified by start and end to the same array starting at position target |
entries | () => ArrayIterator<[number, FlashData]> | Returns an iterable of key, value pairs for every entry in the array |
keys | () => ArrayIterator<number> | Returns an iterable of keys in the array |
values | () => ArrayIterator<FlashData> | Returns an iterable of values in the array |
includes | (searchElement: FlashData, fromIndex?: number | undefined) => boolean | Determines whether an array includes a certain element, returning true or false as appropriate. |
flatMap | <U, This = undefined>(callback: (this: This, value: FlashData, index: number, array: FlashData[]) => U | readonly U[], thisArg?: This | undefined) => U[] | Calls a defined callback function on each element of an array. Then, flattens the result into a new array. This is identical to a map followed by flat with depth 1. |
flat | <A, D extends number = 1>(this: A, depth?: D | undefined) => FlatArray<A, D>[] | Returns a new array with all sub-array elements concatenated into it recursively up to the specified depth. |
at | (index: number) => FlashData | Returns the item located at the specified index. |
__@iterator@147944 | () => ArrayIterator<FlashData> | Iterator |
__@unscopables@147954 | { [x: number]: boolean | undefined; length?: boolean | undefined; toString?: boolean | undefined; toLocaleString?: boolean | undefined; pop?: boolean | undefined; push?: boolean | undefined; concat?: boolean | undefined; join?: boolean | undefined; reverse?: boolean | undefined; shift?: boolean | undefined; slice?: boolean | undefined; sort?: boolean | undefined; splice?: boolean | undefined; unshift?: boolean | undefined; indexOf?: boolean | undefined; lastIndexOf?: boolean | undefined; every?: boolean | undefined; some?: boolean | undefined; forEach?: boolean | undefined; map?: boolean | undefined; filter?: boolean | undefined; reduce?: boolean | undefined; reduceRight?: boolean | undefined; find?: boolean | undefined; findIndex?: boolean | undefined; fill?: boolean | undefined; copyWithin?: boolean | undefined; entries?: boolean | undefined; keys?: boolean | undefined; values?: boolean | undefined; includes?: boolean | undefined; flatMap?: boolean | undefined; flat?: boolean | undefined; at?: boolean | undefined; [Symbol.iterator]?: boolean | undefined; readonly [Symbol.unscopables]?: boolean | undefined; } | Is an object whose properties have the value 'true' when they will be absent when used in a 'with' statement. |
ArgsOf
type ArgsOf<S> = S extends { args: infer Args extends readonly unknown[] } ? Args : unknown[]WriteArgs
| Field | Type | |
|---|---|---|
length | number | Gets the length of the array. This is a number one higher than the highest element defined in an array. |
toString | () => string | Returns a string representation of an array. |
toLocaleString | { (): string; (locales: string | string[], options?: (NumberFormatOptions & DateTimeFormatOptions) | undefined): string; } | Returns a string representation of an array. The elements are converted to string using their toLocaleString methods. |
concat | { (...items: ConcatArray<FlashArgumentLike>[]): FlashArgumentLike[]; (...items: (FlashArgumentLike | ConcatArray<FlashArgumentLike>)[]): FlashArgumentLike[]; } | Combines two or more arrays. |
join | (separator?: string | undefined) => string | Adds all the elements of an array separated by the specified separator string. |
slice | (start?: number | undefined, end?: number | undefined) => FlashArgumentLike[] | Returns a section of an array. |
indexOf | (searchElement: FlashArgumentLike, fromIndex?: number | undefined) => number | Returns the index of the first occurrence of a value in an array. |
lastIndexOf | (searchElement: FlashArgumentLike, fromIndex?: number | undefined) => number | Returns the index of the last occurrence of a specified value in an array. |
every | { <S extends FlashArgumentLike>(predicate: (value: FlashArgumentLike, index: number, array: readonly FlashArgumentLike[]) => value is S, thisArg?: any): this is readonly S[]; (predicate: (value: FlashArgumentLike, index: number, array: readonly FlashArgumentLike[]) => unknown, thisArg?: any): boolean; } | Determines whether all the members of an array satisfy the specified test. |
some | (predicate: (value: FlashArgumentLike, index: number, array: readonly FlashArgumentLike[]) => unknown, thisArg?: any) => boolean | Determines whether the specified callback function returns true for any element of an array. |
forEach | (callbackfn: (value: FlashArgumentLike, index: number, array: readonly FlashArgumentLike[]) => void, thisArg?: any) => void | Performs the specified action for each element in an array. |
map | <U>(callbackfn: (value: FlashArgumentLike, index: number, array: readonly FlashArgumentLike[]) => U, thisArg?: any) => U[] | Calls a defined callback function on each element of an array, and returns an array that contains the results. |
filter | { <S extends FlashArgumentLike>(predicate: (value: FlashArgumentLike, index: number, array: readonly FlashArgumentLike[]) => value is S, thisArg?: any): S[]; (predicate: (value: FlashArgumentLike, index: number, array: readonly FlashArgumentLike[]) => unknown, thisArg?: any): FlashArgumentLike[]; } | Returns the elements of an array that meet the condition specified in a callback function. |
reduce | { (callbackfn: (previousValue: FlashArgumentLike, currentValue: FlashArgumentLike, currentIndex: number, array: readonly FlashArgumentLike[]) => FlashArgumentLike): FlashArgumentLike; (callbackfn: (previousValue: FlashArgumentLike, currentValue: FlashArgumentLike, currentIndex: number, array: readonly FlashArgumentLike[]) => FlashArgumentLike, initialValue: FlashArgumentLike): FlashArgumentLike; <U>(callbackfn: (previousValue: U, currentValue: FlashArgumentLike, currentIndex: number, array: readonly FlashArgumentLike[]) => U, initialValue: U): U; } | Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function. |
reduceRight | { (callbackfn: (previousValue: FlashArgumentLike, currentValue: FlashArgumentLike, currentIndex: number, array: readonly FlashArgumentLike[]) => FlashArgumentLike): FlashArgumentLike; (callbackfn: (previousValue: FlashArgumentLike, currentValue: FlashArgumentLike, currentIndex: number, array: readonly FlashArgumentLike[]) => FlashArgumentLike, initialValue: FlashArgumentLike): FlashArgumentLike; <U>(callbackfn: (previousValue: U, currentValue: FlashArgumentLike, currentIndex: number, array: readonly FlashArgumentLike[]) => U, initialValue: U): U; } | Calls the specified callback function for all the elements in an array, in descending order. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function. |
find | { <S extends FlashArgumentLike>(predicate: (value: FlashArgumentLike, index: number, obj: readonly FlashArgumentLike[]) => value is S, thisArg?: any): S | undefined; (predicate: (value: FlashArgumentLike, index: number, obj: readonly FlashArgumentLike[]) => unknown, thisArg?: any): FlashArgumentLike; } | Returns the value of the first element in the array where predicate is true, and undefined otherwise. |
findIndex | (predicate: (value: FlashArgumentLike, index: number, obj: readonly FlashArgumentLike[]) => unknown, thisArg?: any) => number | Returns the index of the first element in the array where predicate is true, and -1 otherwise. |
entries | () => ArrayIterator<[number, FlashArgumentLike]> | Returns an iterable of key, value pairs for every entry in the array |
keys | () => ArrayIterator<number> | Returns an iterable of keys in the array |
values | () => ArrayIterator<FlashArgumentLike> | Returns an iterable of values in the array |
includes | (searchElement: FlashArgumentLike, fromIndex?: number | undefined) => boolean | Determines whether an array includes a certain element, returning true or false as appropriate. |
flatMap | <U, This = undefined>(callback: (this: This, value: FlashArgumentLike, index: number, array: FlashArgumentLike[]) => U | readonly U[], thisArg?: This | undefined) => U[] | Calls a defined callback function on each element of an array. Then, flattens the result into a new array. This is identical to a map followed by flat with depth 1. |
flat | <A, D extends number = 1>(this: A, depth?: D | undefined) => FlatArray<A, D>[] | Returns a new array with all sub-array elements concatenated into it recursively up to the specified depth. |
at | (index: number) => FlashArgumentLike | Returns the item located at the specified index. |
__@iterator@147944 | () => ArrayIterator<FlashArgumentLike> | Iterator of values in the array. |
__@unscopables@147954 | { readonly [x: number]: boolean | undefined; readonly length?: boolean | undefined; toString?: boolean | undefined; toLocaleString?: boolean | undefined; concat?: boolean | undefined; join?: boolean | undefined; slice?: boolean | undefined; indexOf?: boolean | undefined; lastIndexOf?: boolean | undefined; every?: boolean | undefined; some?: boolean | undefined; forEach?: boolean | undefined; map?: boolean | undefined; filter?: boolean | undefined; reduce?: boolean | undefined; reduceRight?: boolean | undefined; find?: boolean | undefined; findIndex?: boolean | undefined; entries?: boolean | undefined; keys?: boolean | undefined; values?: boolean | undefined; includes?: boolean | undefined; flatMap?: boolean | undefined; flat?: boolean | undefined; at?: boolean | undefined; [Symbol.iterator]?: boolean | undefined; readonly [Symbol.unscopables]?: boolean | undefined; } | Is an object whose properties have the value 'true' when they will be absent when used in a 'with' statement. |
OverrideOptionsArgument
The options argument: optional for a void member, required (with returns) otherwise.
type OverrideOptionsArgument<S> = [ReturnsOf<S>] extends [void]
? [options?: OverrideOptions]
: [options: ReturnsRequired<S>]OverrideOptions
| Field | Type | |
|---|---|---|
when optional | HookWhen | undefined | Only calls this condition holds for; the rest run as ever. See phantom.flash.rules. |
first optional | number | undefined | Only the first this many matching calls. |
every optional | number | undefined | Only every this many matching calls. |
replaceArgs optional | Record<string, unknown> | undefined | Arguments to replace inside the call before the handler gets it, by name or index. |
returns optional | unknown | What the skipped call returns to the client there and then: needed for anything but a void method, as a constant or a value read inside the call (rules.arg, rules.self...). |
perFrame optional | number | undefined | Calls caught in one frame at most: 200, the most the game allows, by default. A call over the limit runs as though the override were not there, which reads as a plugin that half works: in a busy trade room one chat line is transformed and the next is not. So the default is the ceiling rather than something below it, and lowering it is a deliberate choice to let a busy frame through untouched. |
onError optional | "original" | "skip" | undefined | When the handler throws (the error is logged): run the original, if the handler had not (original, the default), or leave the call skipped (skip). |
onDropped optional | ((dropped: number) => void) | undefined | Told how many calls ran without the override in a frame, over perFrame or the calls waiting. Without it the first frame that drops any is warned about on the console, once, so an override that silently let calls through says so. |
sequential optional | boolean | undefined | Run handlers one at a time, in the order the calls came, each after the last has finished. True by default. The client has already moved on by the time a handler runs, so what the order decides is when each original() reaches the client. Side by side, two calls made close together can arrive the other way about, and anything the client appends in order (chat lines, a log, a list) comes out shuffled. That is a bug an author finds in a busy room and not at their desk, so the safe order is the default; sequential: false asks for the quicker one where the order cannot show. |
ReturnsRequired
type ReturnsRequired<S> = [ReturnsOf<S>] extends [void]
? OverrideOptions
: OverrideOptions & { returns: HookValue }OverrideStop
Ends an override. Calls still waiting on the handler end with it, their original not run.
type OverrideStop = (() => Promise<void>) & {
readonly registration: number;
readonly hook: HookEntry;
/** Handlers running now. */
readonly running: number;
/** How many calls it saw, matched and dropped. */
stats(): Promise<HookStats | null>;
}TextStyle
A text field's look as style sets it. Every field may be left out, and is then left as the client has it; background and border take a colour, or false to turn them off.
| Field | Type | |
|---|---|---|
font optional | string | undefined | |
size optional | number | undefined | |
color optional | number | undefined | |
bold optional | boolean | undefined | |
italic optional | boolean | undefined | |
underline optional | boolean | undefined | |
align optional | "left" | "right" | "center" | "justify" | undefined | |
leading optional | number | undefined | |
letterSpacing optional | number | undefined | |
background optional | number | false | undefined | |
border optional | number | false | undefined | |
outline optional | number | null | undefined | A glow of this colour around the letters, to read over anything. |
Held
One value a hold keeps in place: where it goes, and what goes there.
| Field | Type | |
|---|---|---|
place | FlashPlaceLike | The member to write: a typed place, such as phantom.flash.use(...).member. |
value | unknown | What to write: plain data, another place in the game (read each time), or a rule value (phantom.flash.rules.fromClass(...), rules.place(...), rules.constant(...)). |