Pause a script until a message arrives with wait(), always with a timeout, wait for the answer to what you send, and handle what can go wrong.
wait() pauses your code until a message you describe arrives, and hands you that message. It's the tool for "do something, then carry on when the game answers".
wait()
const traffic = await wait(selector, { timeout: 5000 });
wait() returns a promise that resolves with the traffic object of the next message matching selector. The selector is the same as for on(): a message name, "*" for any message, or a function that returns true for the message you want. Listening describes them.
The rest of the bot doesn't pause while you wait. Handlers keep running, and other parts of your code can wait for other messages at the same time.
Only inbound messages
wait() only sees inbound messages: what the game server sends, and what a script or the Network tool sends to the client. A message going to the server never resolves a wait, even with "*". Use onTraffic() to see those.
No timeout unless you pass one
Important
wait() has no timeout of its own. Without { timeout }, a wait that never matches stays open until the script stops, and the code after it never runs. Always pass a timeout.
timeout is a number of milliseconds greater than zero. When it runs out, the promise rejects with protocol wait timed out. That's what gets your code going again when a message doesn't come, whether the game never sends it or its name changed after an update.
| Function | Default timeout |
|---|---|
wait() |
None: it waits until the script stops |
send() |
None needed: it resolves as soon as its message is in line to go out |
request() |
30 seconds |
A wait can't be cancelled. It ends when a message matches, when its timeout runs out, or when the script stops, so give it the shortest timeout that makes sense.
Wait for the answer to what you send
A wait only sees messages the bot handles after wait() was called. It doesn't look back.
Right after send(), that's never a problem. send() resolves as soon as its message is in line to go out, before any answer can reach your code, so a wait started right after await send() always gets the answer, however fast it comes and however busy the PC is:
await send("ChatChannelMessageRequest", { channel: "PARTY", content: text });
const echo = await wait(
(traffic) => traffic.type === "ChatChannelMessageEvent" && traffic.payload?.content === text,
{ timeout: 5000 },
);
What a wait can miss is a message the bot handled before it started: one that arrived while your code was awaiting something else, such as a sleep(). Start a wait as soon as you know you'll need it, not after an unrelated await.
Several waits for one message
- When several waits match the same message, they all resolve with it, in the order they were started.
- Waits resolve after every
on()andonTraffic()handler has run for that message. - A function selector that throws stops the whole script, not only the wait, with an error that includes
evaluate protocol wait selector:. - A script can have 128 waits open at once. One more rejects with
behavior resource limit exceeded: maximum waits reached.
Patterns
Carry on when nothing comes
Catch the timeout and decide what happens next:
try {
const traffic = await wait("InventoryWeightEvent", { timeout: 10000 });
botInfo("Pods:", traffic.payload?.inventoryWeight);
} catch (error) {
// No update can also mean the weight didn't change.
botWarn("No weight update:", String(error));
}
Wait for one of several messages
A function selector can accept several names, and the message you get tells you which one came:
const names = new Set(["InventoryWeightEvent", "InventoryContentEvent"]);
const traffic = await wait((candidate) => names.has(candidate.type), { timeout: 10000 });
botInfo("Received", traffic.type);
Wait inside a handler
Waiting in an async handler doesn't hold up other handlers or messages, which is how the handler gets to see the answer arrive. Put the wait in try/catch: a timeout that escapes a handler stops the script.
This script repeats in the party channel what a party member asks it to say with !say, and checks that the line went through:
import { session, botInfo, botWarn } from "asterobot:bot";
import { on, send, wait } from "asterobot:protocol";
export default async function behavior(launch) {
on("ChatChannelMessageEvent", async (traffic) => {
const content = traffic.payload?.content;
if (traffic.payload?.channel !== "PARTY" || typeof content !== "string" || !content.startsWith("!say ")) return;
const text = content.slice("!say ".length);
// Never repeat a command: the game sends our own line back to us.
if (text === "" || text.startsWith("!")) return;
try {
await send("ChatChannelMessageRequest", { channel: "PARTY", content: text });
await wait(
(echo) => echo.type === "ChatChannelMessageEvent" && echo.payload?.content === text,
{ timeout: 5000 },
);
botInfo("Said in party:", text);
} catch (error) {
// An error that escapes a handler stops the whole script.
botWarn("Couldn't say it:", String(error));
}
});
// A new game connection starts with this message. MITM bots never launch
// with "initial": the Dofus client has already identified their session.
if (launch.reason === "initial") {
await send("IdentificationRequest", {
ticketKey: session.gameToken,
languageCode: session.language,
});
}
}
Errors
wait() never throws: every problem rejects its promise.
| Error | Cause |
|---|---|
protocol wait timed out |
The timeout ran out before a message matched. |
behavior resource limit exceeded: maximum waits reached |
128 waits are already open. |
timeout/milliseconds must be a finite positive Number |
timeout is 0, negative, or not a number. |
timeout/milliseconds is too large |
timeout is too large to be a duration. |
protocol options must be an object |
The second argument isn't an object. |
unknown protocol option "retries" |
The options have a key other than timeout, to and correlated. to and correlated are accepted and ignored. |
protocol selector cannot be empty |
The selector is "". |
protocol selector must be a semantic type, '*', or predicate |
The selector is neither a string nor a function. |
Next, Sending covers send() and request(), and Limits lists every limit.