Reading the game is half of what a script does. Here the chat script learns to answer: when someone says !hello, the bot replies in chat and checks that its reply went through.
Warning
On a MITM bot (man-in-the-middle: the bot relays the game session of the Dofus client you play), everything the script sends goes out as your character. The reply on this page is a real chat message that other players can see.
Try the message by hand first
Before writing code, send the message from the Network tool. If it works there, you know the payload is right.
- On the bot's page, open Tools > Network > Send a game message.
- Keep To the server selected, and pick
ChatChannelMessageRequestin the list of messages. - Type some text in
contentand leave the other fields as they are. Empty fields aren't sent, and achannelleft out meansGLOBAL, the general channel. - Check the payload under This is what will be sent., then click Send ChatChannelMessageRequest.
Your line appears in the game. If ChatChannelMessageEvent is selected in Settings, Traffic also shows your request going Out and your line coming back In.
send()
await send("ChatChannelMessageRequest", { channel: "GLOBAL", content: "Hello" });
send() takes a message name and a payload holding the message's fields. It builds the message and sends it to the game server, and its promise resolves as soon as the message is sent: it doesn't wait for an answer.
The promise rejects when the message can't be built or sent:
| Problem | Error |
|---|---|
| A field the message doesn't have | ChatChannelMessageRequest.contnt: unknown protobuf field |
| A value of the wrong type | ChatChannelMessageRequest.content: expected string |
| A channel name that doesn't exist | ChatChannelMessageRequest.channel: unknown enum symbol "GENERAL" |
| A message name Asterobot can't send | semantic protobuf message is not mapped: MyMessageRequest |
| Sending takes longer than the timeout | The promise rejects after 30 seconds, or after the timeout you pass |
channel takes a channel's name as a string, such as "GLOBAL", "GUILD" or "PARTY", or the channel's number. The Dofus protocol reference lists every channel.
Wait for the answer
The game doesn't answer a chat line with a response. It sends the line to everyone on the channel as a ChatChannelMessageEvent, you included. To know your line went through, wait for that event.
A wait only sees messages that arrive after wait() is called, and nothing guarantees that send() resolves before the game's answer comes in. So start the wait first, then send, and await both together:
await Promise.all([
wait(
(traffic) => traffic.type === "ChatChannelMessageEvent" && traffic.payload?.content === "Hello",
{ timeout: 5000 },
),
send("ChatChannelMessageRequest", { channel: "GLOBAL", content: "Hello" }),
]);
wait() is called first, so it's already listening when the line goes out. Promise.all resolves once both have succeeded, and rejects as soon as either one fails. Two other ways of writing this look fine and aren't: a wait started after await send() can miss an answer that came quickly, and a wait kept in a variable to await after send() is left behind when send() fails. Waiting explains both.
wait() resolves with the next inbound message that matches, and you choose what matches: a message name, or a function that returns true for the message you want. The function above checks the name and the text, so another player's line doesn't count.
wait() has no timeout of its own. A wait that never matches stays open until the script stops, so always pass { timeout }, in milliseconds. When the time runs out, the promise rejects with protocol wait timed out.
request()
request() sends a message and resolves with the game's response to it. Only a handful of Dofus requests get a response, matched to them by uid, and none of them is something a first script needs to send. For every other message nothing comes back: request() waits until its timeout, 30 seconds unless you pass another one, and then rejects with context deadline exceeded.
So for almost everything, wait for the event the game answers with while you send(), as this page does. Sending covers request(), and the to option that sends a message to the Dofus client instead of the server.
Timeouts
| Function | Default timeout | To change it |
|---|---|---|
send() |
30 seconds | { timeout: milliseconds } as the third argument |
request() |
30 seconds | { timeout: milliseconds } as the third argument |
wait() |
None: it waits until the script stops | { timeout: milliseconds } as the second argument |
A timeout must be a positive number of milliseconds. Anything else rejects with timeout/milliseconds must be a finite positive Number.
The complete script
import { session, botInfo, botWarn } from "asterobot:bot";
import { on, send, wait } from "asterobot:protocol";
const TRIGGER = "!hello";
const REPLY = "Hello from my first script";
export default async function behavior(launch) {
on("ChatChannelMessageEvent", async (traffic) => {
// Only the exact trigger: the game also sends our own reply back to us,
// and answering that echo would start a loop.
if (traffic.payload?.content !== TRIGGER) return;
try {
// The wait starts before the reply goes out, so a quick echo isn't missed.
await Promise.all([
wait(
(echo) => echo.type === "ChatChannelMessageEvent" && echo.payload?.content === REPLY,
{ timeout: 5000 },
),
send("ChatChannelMessageRequest", { channel: "GLOBAL", content: REPLY }),
]);
botInfo("Reply delivered");
} catch (error) {
// An error that escapes a handler stops the whole script.
botWarn("Reply not delivered:", 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,
});
}
}
The handler is async so that it can await. Handlers don't wait for each other: while this one waits for the echo, the bot keeps handling other messages, the echo included, which is how wait() gets to see it.
The try/catch keeps a failed reply from ending the script, and String(error) turns the error into text the console can print.
Anyone who says !hello on a channel your character receives gets an answer in the general channel, so stop the script when you're done testing.
Test it
- Run the package on your MITM bot.
- Say
!helloin the Dofus chat. - The bot answers in the general channel, and its Console shows
Reply delivered.
If the console shows Reply not delivered: followed by a reason, select ChatChannelMessageRequest and ChatChannelMessageEvent in the Network tool's Settings, try again, and check in Traffic whether the request went out and whether the line came back.
Next, Add a setting lets people change the trigger and the reply without editing your code. Waiting covers every way to wait for a message.
Aucun avis à afficher.