The client, as TypeScript
Phantom ships the whole client as TypeScript declarations, one file per class, generated from the client Phantom serves. Your editor completes class names, members and arguments, and flags it when the client changes.
const manager = phantom.flash.use('avatar.AvatarManager');
const name = await manager.playerAvatar.userName.$get();One file per class
The declarations follow the game's own layout, one class per file. The name in the game is the name you write.
| In the game | As a name you write |
|---|---|
gui.ColorTable | phantom.flash.use('gui.ColorTable') |
avatar.AvatarManager | phantom.flash.use('avatar.AvatarManager') |
it.gotoandplay.smartfoxserver.SmartFoxClient | the same, in full |
MainFrame (no package) | phantom.flash.use('MainFrame') |
Each class has an instance side and a static side, and the static side has $new if the class can be constructed. Private, protected and internal members are declared alongside the public ones, so your editor completes those too.
To browse, type a class name into phantom.flash.use(...) and your editor lists its members and their arguments. Parameters keep Animal Jam's names, so createAvatar completes with userName rather than a number.
Names TypeScript can't take
Some ActionScript names aren't valid in TypeScript. These rules are fixed, so you can always work out the TypeScript name from the game's.
| Where | Rule | Example |
|---|---|---|
| A name TypeScript keeps for itself | $ on the end, and any character an identifier cannot hold becomes _ | flash.debugger is written flash.debugger$ |
| An embedded class with odd characters | the same rule | torch_swf$33fd…-1098895790 becomes …_1098895790$ |
| A member named a reserved word | quoted where it is declared, read with brackets | 'default': number, read as place['default'] |
| Two classes of one name in one file | the first keeps its name, the rest are imported as <package>$<Name> | loader$Loader |
| An overload | there are none: ActionScript has no overloading, so nothing is resolved by signature | - |
| A private name a class and its parent share | one TypeScript key cannot be both, so the public one, or the one declared highest, is the one declared | reach the other by its namespace, below |
Only renamed names end in $, so a rename can't clash with an existing name. A name that can't be made to work at all is left out rather than written wrong, so two classes never end up sharing a file.
Two things are left undeclared on purpose. Both can still be reached at runtime:
- A private member declared by both a class and one of its ancestors or descendants. Both exist in the game, each in its own namespace, but one interface key can't say which you mean. The plain name reaches the one declared highest. For the other, use its full name, as in
place.$at('phantom:private:gui:ColorTable::_colorIndexes'). - Package-internal and file-private classes. The running client calls these
phantom:internal:<package>::<Name>, and a module can't export a name with colons in it. Ask for it by the client's name, as inphantom.flash.use('phantom:internal:org.osmf.net.drm::DRMServices').
A missing declaration only means less help from your editor. Reading, writing and calling all go through reflection, which doesn't need declarations. A place for an undeclared class accepts any member and any call, and each one gives you another untyped place.
const asset = phantom.flash.use('some.loaded.Asset');
console.log(await asset.state.frame.$get());
await asset.play(1);None of that is type checked, so a name the game doesn't have fails when the code runs. The script editor behaves this way for a moment after it opens too, because the SDK is ready before the client's declarations finish loading.
Reaching the client shows how much of the client is declared. Animal Jam's own packages are declared in full: gui, avatar, room, quest, buddy, den, shop, pet, item, game and it.gotoandplay have no gaps. The undeclared classes are package-internal, file-private, or compiler-generated with no members of their own, and you can still reach them by their client name.
A Vector is not an array
Where the client declares Vector.<T>, the types say Vector<T>, not T[], and an array won't work in its place.
In ActionScript each parameterised Vector is its own class, created the first time something names it. Vector.<shop.MyShopItem> is as different from Vector.<String> as either is from Array, and nothing converts between them. Pass an array to a parameter declared Vector.<T> and the client throws TypeError #1034 before the method body runs. This broke the den shop: ShopToSellXtCommManager.requestStoreUpdateItems takes three of them, the SDK was sending three arrays, and listing, repricing and taking items down had never worked.
Ask the bridge for a real one:
const vector = await phantom.flash.bridge.vector('shop.MyShopItem', items);
try {
await phantom.flash.bridge
.definition('shop.ShopToSellXtCommManager')
.at('requestStoreUpdateItems')
.call(4, 'state-uuid', vector, vector, vector, () => undefined, null);
} finally {
await phantom.flash.bridge.release([vector]);
}It gets the class from the client's own application domain and fills the vector one push at a time, because new Vector.<T>(length) takes a length, not contents. Give it the item's class dotted, as shop.MyShopItem, or String, int or * for a top-level type.
Vector is the only type here written by hand rather than read from the client. The bytecode holds a parameterised Vector as a type rather than a class with members, and Adobe doesn't ship it in playerglobal.
Package map
| Package | What is in it |
|---|---|
gui | Almost every screen: AvatarEditor, DenEditor, MySettings, ColorTable, GuiManager (the layers), GuiHud, WindowAndScrollbarGenerator (every scrolling list and grid) |
avatar | AvatarManager (who is in the room and how they are drawn), Avatar, AvatarSwitch, AvatarXtCommManager (the avatar's messages to the server) |
room | RoomManagerWorld, loading a room, its layers and its volumes |
item, inventory, collection | What you own, and the collections the screens sort and filter |
shop, trade, den, pet, buddy, ecard, playerWall, quest, achievement | One area of the game each, screen and messages together |
it.gotoandplay.smartfoxserver | The SmartFox client: every message to and from the server goes through SmartFoxClient.sendXtMessage |
com.sbi | Animal Jam's own engine: graphics (PaletteHelper, LayerBitmap, how an animal is painted), popup (every dialog), loader (assets) |
game | The minigames, each in its own subpackage |
gamePlayFlow, createAccountFlow, waitingRoomFlow | Starting up, logging in, and what happens before the world |
loader, resource, localization | Loading files, media and text |
Start by finding the screen in gui/ and reading its init to see what it builds, then look at the Xt class in the same part of the game to see what it sends the server.
That means reading Animal Jam's ActionScript. Reading the client shows how to get it: one command writes the whole client out as .as files laid out like these declarations, so gui/ChatHistory.as matches gui/ChatHistory.ts. The declarations say what a member is, and the source says what the client does with it. The same page covers phantom.flash.describe and phantom.flash.search, for finding what a class holds and what something is called without leaving Phantom.