Skip to content

Chat & private messages ​

Mytharyn has two kinds of player text:

  • Chat is the bubble above a player's head, seen by everyone in the same level. Scripts can read it, set it and react to it. A chat starting with / is a local command instead: it's never shown and never leaves the client.
  • Private messages (PMs) go from one account to another, across servers. They're handled by the Login Server's global PM system. Game-server scripts can send system PMs and learn that a PM arrived, but can't read PM text.

Chat bubbles ​

A player's chat is one string. Setting it shows a bubble to everyone in their level, and '' clears it. The bubble fades 5 seconds after its owner moves. Text is trimmed to 200 characters and control characters become spaces. Players who enter the level later still see an existing bubble.

SideReadWrite
Serverplayer.chat (live)player.chat = 'text': applies immediately, shown to everyone in the level including the player
Clientplayer.chat (local player)player.chat = 'text': batched, shown locally and sent to the server like typed chat
ts
// Server: make a player say something.
player.chat = `*gulp* ${potionName}`

Reacting to chat on the client: onPlayerChats ​

Clientside weapons (and clientside NPC scripts) export onPlayerChats(player, chat):

ts
function onPlayerChats(who: ChatPlayer, chat: string) {
    if (who.id !== player.id) return            // only react to yourself
    if (chat === '/where') echo(`${player.level} ${player.x},${player.y}`)
}

It fires when any player in the level changes their chat, you included. That covers typed chat, chat set by a clientside script, and chat set by a server script. chat is '' when a bubble was cleared. The first argument is a frozen ChatPlayer snapshot: id, name (account), nick, x, y, dir, and chat as it was at that moment.

Only actual changes fire it. Setting the same text again is silent, so a handler that reacts by writing player.chat can't loop on itself. It does not fire when a bubble expires, or for bubbles that already existed when their owner entered your level.

Slash commands ​

Chat that starts with / is a command:

  • no bubble is shown, and nothing is sent to the server or other players;
  • it's delivered to onPlayerChats on this client only, with who being the local player and chat the full command text.

That's all you need to build chat commands. Parse the text, and call triggerServer for anything the server must do:

ts
// weapons/commands.client.ts
function onPlayerChats(who: ChatPlayer, chat: string) {
    if (who.id !== player.id || !chat.startsWith('/')) return
    const [cmd, ...args] = chat.slice(1).split(' ')
    switch (cmd) {
        case 'ani':  setAni(args[0]); break                               // purely local
        case 'home': triggerServer('weapon', this.name, 'home'); break    // needs the server
    }
}

Every loaded weapon receives every command. Ignore ones that aren't yours, and pick distinctive command names. The Login Server's -pm weapon also hears your / commands on every server (that's how /pm <account> <text> and /r <text> work everywhere), so avoid those two names.

Built-in commands

The client handles a few commands itself, without a slash: setnick <name> (bare setnick resets to your account name), sethead <image>, setbody <image>, setcolor <slot> r,g,b (slot 0–4 or skin/coat/sleeves/shoes/belt) and showname. The first four still show as normal chat. showname is consumed.

Commands you want other players to see (say, an :additem command) can use any other prefix. They're then ordinary chat, broadcast as a bubble and delivered to everyone's onPlayerChats.

Reacting to chat on the server ​

Plain server scripts and weapon server halves have no chat event. Two ways to react to chat serverside:

  1. Forward from a clientside weapon. Catch it in onPlayerChats and triggerServer it. This is the usual way for commands, and it works for /commands, which never reach the server otherwise.
  2. Level NPCs. A serverside NPC script's onPlayerChats(player, chat) fires for chat typed by players in its level. Script-set chat and /commands don't trigger it. A shopkeeper can open its shop when someone says "shop" nearby. See NPCs.

Private messages ​

PMs are global. The Login Server's persistent -pm weapon (F8) keeps the friends list, holds received messages and lets players read and send them on every server. For the privileged API behind it, see Login server scripting. Game-server scripts get two narrow hooks.

Sending a system PM: sendPM (server) ​

sendPM(target, text) sends a system PM from this server to a player on any server. target is an account name or a Player.

ts
sendPM(player, 'Welcome back!')
sendPM('SomeAccount', 'Your auction sold for 500 gold')
  • It needs no player context, so it's safe after an await or in a timer.
  • It's delivered only if the player is online somewhere. Otherwise it's silently dropped.
  • Text is limited to 200 characters. The call is batched.
  • The recipient sees it as coming from your server: { account: <server name>, server: <server name>, system: true }.

Knowing a PM arrived: onPMReceived (client) ​

Clientside weapons and clientside NPC scripts can export onPMReceived(sender):

ts
function onPMReceived(sender: PMSender) {
    if (!sender.system) echo(`New message from ${sender.account} (${sender.server})`)
}

PMSender has account (the sender, or the sending server's name for system PMs), server (the server they're on, '' if unknown) and system (true for sendPM from a server script). The event is a notification only. The message text is never delivered to game-server scripts. Only the Login Server's privileged weapons can read it (with openPM), so a game server can't snoop on players' conversations. Game weapons also can't send player PMs or see the friends list.