Chat and commands¶
Every player has a chat, as SA-MP's: the message log top-left, and an
input line, opened with T or F6. Enter sends it, Escape closes it,
Up/Down go back through what they sent; Ctrl+C, Ctrl+X, Ctrl+V and Ctrl+A
copy, cut, paste and select all. While it's open, PageUp/PageDown
and the mouse wheel scroll back through the log (its last 100 lines).
Messages (player.send, server.broadcast)
colour parts of themselves with {RRGGBB}, as SA-MP's.
/ opens it with the slash already typed. A line is either chat or a /command. Typing a command, the chat suggests as Minecraft's does: the commands they can use, how each is used (the argument being typed marked), and words for it (subcommands, choices, player names, your own suggestions). Tab takes the suggestion, Up/Down choose another. Commands are listed alphabetically, except the last ten they used, which come first, newest first (remembered between sessions).
Chat¶
What a player says goes to everyone as server.chat.format makes it
(default Name: text, the name in player.color).
playerChat sees it first:
server.on("playerChat", (player, text) => {
// Local chat, as roleplay servers do: only those near hear it.
for (const p of server.players) {
if (p.isNear(player.position, 20)) p.send(`${player.name} says: ${text}`, 0xe6e6e6);
}
return false; // not to everyone
});
Return false to drop the line, or a string to send instead of their text.
server.chat |
Default | |
|---|---|---|
format(player, text) |
"{RRGGBB}Name{FFFFFF}: text" |
A line as everyone sees it. |
floodLines |
5 | The flood limit: a burst of this many lines... |
floodPerSecond |
1 | ...then this many a second. Past it: "You're sending messages too fast." |
The player's own commands¶
Every player's chat has a few of its own, handled on their game (as SA-MP's client commands), and suggested like the rest:
| Command | |
|---|---|
/quit, /q |
Leaves the game. |
/pagesize <5-30> |
How many chat lines show (remembered, in sagl.ini). |
/timestamp |
Times on chat lines, on or off (remembered). |
/clear |
Empties their chat. |
/save [note] |
Their position, heading and interior, written to sagl/savedpositions.txt as code to paste ({ x: ..., y: ..., z: ... }, heading): for building gamemodes. |
A command of the server's or a client script's with the same name takes its place.
Clearing it¶
player.clearChat() empties one player's log, server.clearChat()
everyone's; a client script's sagl.chat.clear() its own player's.
Commands¶
server.commands.register(...commands)¶
Commands are built up, as Minecraft's Brigadier: a name, arguments, subcommands, who may use it, and what it does.
server.commands.register(
command("pay").description("Give money to a player").alias("give")
.argument("player", arg.player())
.argument("amount", arg.integer({ min: 1 }))
.executes((player, { player: to, amount }) => {
if (player.money < amount) throw new CommandError("You don't have that much");
player.money -= amount;
to.money += amount;
}),
command("me").description("An action").argument("action", arg.text())
.executes((player, { action }) => server.broadcast(`{C2A2DA}* ${player.name} ${action}`)),
command("faction").requires((p) => p.data.leader === true)
.then(command("invite").argument("player", arg.player()).executes((p, { player }) => { ... }))
.then(command("kick").argument("player", arg.player()).argument("reason", arg.text(), { optional: true })
.executes((p, { player, reason }) => { ... })),
);
CommandBuilder |
|
|---|---|
command(name) |
Starts one. |
.alias(...names) |
Other names for it. |
.description(text) |
Shown with its usage in the suggestions. |
.requires(fn) |
fn(player): who may use it, and see it suggested. |
.argument(name, type, { optional? }) |
The next argument (optional ones last; arg.text() takes the rest of the line). |
.then(sub) |
A subcommand: /faction invite .... |
.executes(fn) |
fn(player, args, line): args by name. Throw CommandError to tell them what's wrong (their usage follows). |
Wrong usage is answered for you, as Minecraft's: what's wrong ("Missing
amount", "x" isn't a whole number, More than one player matches "tom"),
then how it's used (Usage: /pay <player> <amount>).
| Argument | Takes | Gives |
|---|---|---|
arg.player() |
an id, a name, or the start of one (one match) | Player |
arg.integer({ min?, max? }) |
a whole number | number |
arg.number({ min?, max? }) |
a number | number |
arg.word() |
one word | string |
arg.text() |
the rest of the line | string |
arg.boolean() |
yes/no, on/off, true/false | boolean |
arg.choice([...]) |
one of these words (suggested) | the word |
arg.vehicle() |
a vehicle's id | Vehicle |
arg.custom({ label, parse, suggest? }) |
yours: parse(input, player) (throw CommandError to refuse), suggest(player, partial) the words the chat suggests (asked of the server as they type) |
what parse returns |
const job = arg.custom({
label: "job",
parse: (s) => { if (!JOBS.includes(s)) throw new CommandError(`No job "${s}"`); return s; },
suggest: (player, partial) => JOBS.filter((j) => j.startsWith(partial)),
});
server.commands |
|
|---|---|
register(...commands) |
Adds them; players' chats suggest them at once. |
unregister(name) |
Removes one. |
refresh(player) |
Sends their chat the commands they can use now: after what requires checks changes (an admin level, a job). |
Old-style commands¶
A /command no registered command is called goes to playerCommandText
with the whole line, as SA-MP's OnPlayerCommandText: return true if you
handled it; otherwise they're told it's an unknown command. A port can
start there and move commands over one at a time.
server.on("playerCommandText", (player, text) => {
const [cmd, ...params] = text.split(" ");
if (cmd === "/kill") { player.health = 0; return true; }
});
Client scripts¶
Client scripts have the chat too (sagl.chat)
and commands of their own (sagl.commands),
built the same way, suggested with the server's, run on their game.