Playbooks
Playbooks in the sidebar is a visual builder for automating the game. A playbook is a list of steps: call something in the game, wait, check what came back, branch, loop, stop. You build it without a game running, check what it would do, then run it against one of your games.
A script is code you write and run. A playbook is steps you put together and read first: you have to press Check the plan before Run does anything, and the plan lists whatever can't be undone at the top.
A playbook has three tabs: Flow (its steps), Plan and Run. Flow is only the tab, not another name for the feature, which is Playbooks everywhere: playbooks.json, the /playbooks route and the @phantom/playbook package.
What's in a playbook
| Part | What it is |
|---|---|
| Steps | What it does, in order. Branches and loops hold steps of their own. |
| Trigger | What starts it: by hand, on a timer, at login, or when the game says something happened. |
| Scope | Which account it's written for. Most are written for any of them. |
| Description | What it's for, in your words. This is the first thing anyone you send it to reads. |
Playbooks are kept in a file on this computer (playbooks.json, next to your scripts), not in the window's storage, so clearing a cache won't delete them and every window sees the same list. Save to a file exports one as JSON, and the upload button in the list opens one someone sent you.
Steps
Every call in the SDK is available as a step. The palette on the left is built from the SDK's permission table, so a playbook can call anything a plugin can.
These steps give a playbook its structure:
| Step | What it does |
|---|---|
call | One call of the SDK, with arguments you write. Keep the answer under a name with into. |
set | Work out a value and put it in the run's memory under a name. |
when | Run one branch or the other, depending on a test. |
for-each | Run some steps once for each item in a list, with each one bound to a name. |
while | Run some steps over and over while a test holds, up to most times. |
wait | Wait, in seconds. It can be a range, so the timing isn't the same every time. |
await-event | Wait for the game to say something happened, and keep what it said. |
confirm | Ask, and carry on down the branch you chose. |
note | Write a line in Phantom's log, for whoever reads the run afterwards. |
stop | Stop here, successfully or not. |
Any step can also have:
note: why the step is there. The generated explanation says what a step does, but only you know why.ask: stop and ask you before the step runs. On by default for anything that can't be undone.keepGoing: carry on with the rest of the playbook if this step fails, instead of ending the run.
Expressions
Anywhere a step wants a value, you can write an expression instead. Expressions read what earlier steps put in the run's memory, so a condition can look at what the game just returned.
| Expression | What it gives |
|---|---|
literal | What you typed. |
read | A name in memory, reached into with dots: trade.items.0.name. |
text | Words with values in them: Trading {item} for {price} gems. |
compare | is, is-not, above, at-least, below, at-most, contains, starts-with, ends-with, matches. |
every, some, not | And, or, and not. An empty every is true; an empty some is false. |
count | How many are in a list, or how many characters in a word. |
at | One of a list by position, counting from zero. Out of range is null. |
filter | The ones in a list a test is true of. |
each | One value out of each of a list: the names out of a list of items. |
math | plus, minus, times, divided-by, remainder. |
random | A whole number from low to high, both ends included. |
any-of | One of a list at random. An empty list is null. |
first-of | The first of these that isn't null, so a missing value can have a fallback. |
Random waits and lines
Automation that waits exactly sixty seconds every time and says the same line in the same order is easy to spot, for players and for Animal Jam. Give wait a range with random, and give a chat step a list of lines with any-of.
{
"type": "wait",
"seconds": {
"of": "random",
"low": { "of": "literal", "value": 45 },
"high": { "of": "literal", "value": 90 }
}
}Triggers
| Trigger | When it runs |
|---|---|
by-hand | When you press Run. |
every | Every so often, for as long as the playbook is on. |
on-event | When the game says something happened: any of the SDK's on* members. |
on-ready | Once the game is ready to be automated, so a playbook can run itself at login. |
The plan
Check the plan works out what the playbook would do before it does any of it: every step in the order it would be reached, the permissions it needs, the longest it could take, and the steps that can't be undone.
Changing anything makes the plan out of date, and Run does nothing until you check it again, so what runs is always what you approved.
A lot of what a playbook does depends on what the game sends back, so each step in the plan is marked as reached always, only down one branch, or every time round a loop. A playbook with a while in it has no longest time, and the plan says so.
There is no undo
A trade you sent is sent. Gems you spent are spent. A deleted animal is gone, along with everything only it owned. Nothing in Playbooks can put any of that back.
Instead you get asked. Every call in the palette says whether it can be undone, and for the ones that can't, ask is on from the moment you add the step. When the run reaches it, it stops and shows you what it's about to do, in red. Saying no skips the step and the run carries on.
You can turn ask off, and the inspector tells you what that means while you do it.
The Run dialog lists the same steps first. If someone else wrote the playbook, you also have to tick a box with their name on it before you can type the confirmation, because a playbook you didn't write still acts as you, on your account.
Pacing
Some calls are things Animal Jam keeps count of: chat, anything other players see or receive, joining or leaving a room, and calls it throttles by name. A step that makes one of these calls waits thirty seconds before making it again. That's roughly how fast a person would do it by hand. Animal Jam mutes accounts that repeat themselves faster than that, and drops the connection of anything faster still.
The wait is per step, not per run, so a list of different actions isn't slowed down. It's loops coming round again that it holds back. Reads, walking and anything that only changes what's drawn on this computer aren't paced.
Running
While a playbook runs, the Run panel shows every step as it happens: what it did, what came back, what failed and what it's waiting for. If a run fails halfway, the log shows where.
If Animal Jam drops the connection, Phantom restarts the game, and a call waits for it to come back instead of failing. A step waiting on an event is told straight away, because its subscription went with the old game.
Stop ends the run at once. Every wait ends, any open question is answered no, and every listener is removed. A call already on its way to the game can't be recalled, but nothing after it runs.
Finished runs are kept in playbook-runs.json, so you can still read a failure tomorrow. The last twenty five are kept.
Examples
Say something now and then, in different words
Three lines, one picked at random, with a gap of forty five to ninety seconds, repeating until you stop it.
{
"name": "Trade advert",
"description": "Says one of three lines while I'm sat in a trading room. Nothing else.",
"trigger": { "type": "by-hand" },
"steps": [
{
"id": "lines",
"type": "set",
"name": "lines",
"value": {
"of": "literal",
"value": ["Trading spikes", "Looking for rares", "Offer on my den items"]
}
},
{
"id": "loop",
"type": "while",
"test": { "of": "literal", "value": true },
"most": 40,
"steps": [
{
"id": "say",
"type": "call",
"path": "chat.say",
"args": {
"text": { "of": "any-of", "over": { "of": "read", "name": "lines" } }
},
"note": "One of the three, picked at random each time."
},
{
"id": "gap",
"type": "wait",
"seconds": {
"of": "random",
"low": { "of": "literal", "value": 45 },
"high": { "of": "literal", "value": 90 }
}
}
]
}
]
}The loop's test never changes, so the plan says this one has no longest time. The most of 40 is what ends it.
Accept a trade only if it has what you want
Wait for the game to say something, look inside what it said, and decide.
{
"name": "Watch for a spike",
"description": "Accepts a trade only if it offers a spike. Asks me first either way.",
"trigger": { "type": "by-hand" },
"steps": [
{
"id": "wait-for-it",
"type": "await-event",
"path": "trading.onRequest",
"into": "trade",
"timeoutSeconds": 600
},
{
"id": "spikes",
"type": "set",
"name": "spikes",
"value": {
"of": "filter",
"over": { "of": "read", "name": "trade.items" },
"as": "item",
"where": {
"of": "compare",
"left": { "of": "read", "name": "item.name" },
"is": "contains",
"right": { "of": "literal", "value": "Spike" }
}
}
},
{
"id": "decide",
"type": "when",
"test": {
"of": "compare",
"left": { "of": "count", "over": { "of": "read", "name": "spikes" } },
"is": "above",
"right": { "of": "literal", "value": 0 }
},
"then": [
{
"id": "accept",
"type": "call",
"path": "trading.accept",
"args": {},
"ask": true,
"note": "Accepting is final, so this one always asks, however sure the test is."
}
],
"otherwise": [
{
"id": "say-no",
"type": "note",
"text": { "of": "literal", "value": "Nothing in that trade, left it alone." }
}
]
}
]
}Careful
A playbook acts as you, in your game, straight away. Automating play puts your Animal Jam account at risk, and Phantom can't take that risk away. Read the plan, leave ask on for anything final, and don't shorten the waits.
What a playbook can't do
Everything a playbook does goes through the SDK, so if there's no step for something, the SDK doesn't have that call yet. A few calls can't be steps at all, and the palette marks them:
- Anything drawn where it's called, like
phantom.aj.ui. Those run inside a plugin's own frame, so there's nothing to hand back to a step. - Anything that returns a way to stop it, like
currency.watch. Use a step that waits for an event instead. - The game client itself (
phantom.flash) and raw traffic. Use a script or a plugin for those.