TPN Script Reference

TPN script is a “scripting language” of sorts that I am using in my Twitch chat.

Note

This syntax was initially devised for Twitch Plays Noita, so the following reference will use things like pressing keys and mouse movement in examples.

Command structure

A single simple command looks like this:

any text command~ any other text

A command that accepts arguments can look like this:

some more text here too command:arg1:arg2~

The standard format for commands is a ~ suffix. Historically, this has happened because I “didn’t want to be like the rest”. Almost immediately, a + prefix was also introduced for mobile users, and there was this whole thing about collecting statistics on which version was used more, but that idea was eventually scrapped.

A bit later, the lenient command parser was introduced, which basically allows most typical symbols to be command prefixes.

Here’s a list of acceptable syntaxes, there’s no difference between them:

  • kawaii~ - the initial one
  • +crusader - the anti-weeb one
  • ~prefix - because people sometimes forgot ¯\_(ツ)_/¯
  • !normie - to pretend to be any other bot
  • ?question-mark - I’ve seen some bots use that..
  • #hashtag - ..and that
  • silent:arg1 - if there’s at least one argument, meaning : is present, you can just drop the symbol altogether actually.

Another small quirk is commands of the form command123~ would first try to run a command or macro named command123, but if neither exist it would also try command:123~, making the number into a first argument.

This is a shorthand to avoid typing : as commands having a single numerical argument were quite common in TPN.

Sequential commands

So commands can occur anywhere in the message and use this weird argument syntax for a reason: a message can have multiple commands, they will be executed one after the other, in parallel with all other messages(!).

This matters because certain commands take some time to execute, an obvious example being wait~, which does nothing for 500ms.

As an example, you can do this:

left~ wait~ wait~ right~

Which will hold the a button for 500ms (the default), wait for a second and then hold d for another 500ms.

Parallel commands

What if you want to hold the d key and tap the w key at the same time to levitate? Or to climb an obstacle? Run and shoot?

All command sequences separated with a | symbol start and execute in parallel.
The / symbol would also work, it was added for mobile users.

So tap-levitating while moving to the right could look something like this (note that I haven’t tested those particular timings):

up:100~ wait:400~ up:100~ wait:400~ up:100~ wait:400~ | right~ right~ right~
Note

As a general rule, commands that need or can wait for a variable amount of time, so holding down keys, wait~ etc, accept an optional argument for the number of milliseconds with default usually being 500.

Commands after the | start executing immediately, in parallel with the commands before it - so in this example you press d for 1.5 seconds, while tapping w 3 times every 0.5 seconds for 0.1 seconds each.

You can have any number of commands and any number of parallel commands, the only limit is the 500-character Twitch limit.

Advanced

Scope creep happened, and during TPN I added countless requested features, starting with macros and loops and ending with variable substitution and math.

Note

In the following examples I use echo excessively, but in my twitch chat echo is something only mods can use because it straight up makes the bot say whatever you give it, with no indication that it’s in response to your echo commands.

You can replace any echo invocation with something like let:x:"whatever" get:x if you want to debug things or something.

I need to add a viewer-available debug print command, or make a global macro for that let-get thing I just suggested at least..

All command parameters are strings. You can do something like

repeat:10:" echo:\"hello world\" "

and as you can see, the repeat command evaluates the string given to it in the second parameter, calling any commands contained within N times.

Another thing you can see, is that nested strings require those ugly escapes (\") for it to work properly.

Brace syntax

So another string syntax exists, this time using balanced braces:

repeat:10:{ echo:{ hello world } }

Two important points:

  • The second argument given to repeat is still just a string.
  • Brace strings are trimmed, that is, { hello } is equivalent to "hello", not " hello ".

Variable substitution

You can set persistent personal variables using the set command, or temporary ones only visible inside of the current message with the let command.

There are a couple more variable manipulation commands in the full list below.

In any string, if you have a bit that looks like %name, it would be replaced by the contents of that variable - or by nothing if the variable was not set.

let:name:necauqua echo:{ Hello, %name! }~

Repeats and loops expose a variable named i to the script they’re evaluating:

repeat:10:{ echo:{ countdown %i } }

Macros expose the arguments they are given when called as numbered variables:

macro-record:test:{ echo:{ hello, %1 } } test:world

There is also a syntax for default values in case the variable does not exist:

echo:{ Hello, %(name:stranger)! }

The default value (stranger) in this case, also gets variable expansion, recursively:

echo:{ Hello, %(1:%(default-name:stranger)) }

That last example first tries to use the variable 1 (first macro argument), then the variable default-name and then defaults to stranger.

Math expressions

There is actually a third string syntax, this time using parenthesis:

echo:(2 + 2 * 2) <- this will print 6

This syntax forces the given expression to be evaluated as if the command expected a number, which means it would be coersed to a number (which includes passing it through a simple calculator) if possible, and error otherwise.

This works exceedingly well with string substitution:

repeat:10:{ let:double:(%i * 2) echo:%double }

For example to make an increasing counter:

macro-record:count:{ set:counter:(%(counter:0) + 1) echo:{ counted %counter times } }
count~
count~
count~

Syntax debug

Using the exact same code that the bot uses to parse commands (thanks to WASM), I made a small website that parses commands and shows you which of them and in which order with what substitutions the bot will see.

The site has no knowledge of any existing commands, it just checks the syntax, and if a string parameter looks like it contains commands, it will be parsed out and shown in a tree, its quite neat.

Play with it at https://uq.rs/lexer - hosted on my other server that’s not in Ukraine like this site, because Ukraine blocks traffic from one particular country for free, I appreciate the feature, but some people have had the critical lack of luck to be born there sadly.

The list of all commands

This is is auto-generated, so it is up to date, there are no hidden commands or parameters 😉

Note

As of right now, this list still includes TPN-specific commands, notably ones for controlling Noita or OBS.

Such commands are obviously disabled and would do nothing, at least until I run TPN 2.0 in a year or so.

The question mark after the argument name means that it is optional.
Shortcodes are alternative short names for certain commands.
The >= thingie means that the command requires that permission level or above.

Permission levels

I am obviously just looking at the message badges lmao
  • (7) Caster - Only the broadcaster can use this command.
  • (6) Moderator - Channel moderators and above can use this command.
  • (5) TwitchAdmin - Twitch administrators and above can use this command.
  • (4) TwitchStaff - Twitch staff and above can use this command.
  • (3) Vip - Channel VIPs and above can use this command.
  • (2) Verified - Verified users (partners) and above can use this command.
  • (1) Subscriber - Channel subscribers and above can use this command.
  • (0) Viewer - Anyone can use this command.

Data

  • seed~ >=Vip

    Read the current seed

    Global timeout: 15s

  • death-count~ shortcode: deaths

    Read the current death count

    Global timeout: 15s

  • kicks~

    The amount of kicks registered by the game in the current run

    Global timeout: 15s

  • perks:top-n?~

    Read the currently picked up perks. Look ma, streamer wands at home!

    Global timeout: 5s

    Arguments:

    • top-n: a non-negative number, optional
  • damage-multipliers~ >=Vip

    Read the current non-1 damage multipliers of the player entity.

    Global timeout: 5s

  • check-flag:flag~

    Checks if the persistent flag was set in the running save.

    Global timeout: 5s

    Arguments:

    • flag: string
  • pillar-progress~

    Shows the amount of completed pillars vs total.

    Global timeout: 5s

  • pillar-todo:top-n?~

    Shows all the pillar achievements not yet completed in the running save.

    Global timeout: 5s

    Arguments:

    • top-n: a non-negative number, optional
  • pillars-done:top-n?~

    Shows all the pillar achievements already completed in the running save.

    Global timeout: 5s

    Arguments:

    • top-n: a non-negative number, optional
  • spell-progress~

    Shows the amount of unique spells ever cast vs total.

    Global timeout: 5s

  • spell-todo:top-n?~

    Shows all the spells that were never cast in the running save.

    Global timeout: 5s

    Arguments:

    • top-n: a non-negative number, optional
  • spells-done:top-n?~

    Shows all the spells that were already cast in the running save.

    Global timeout: 5s

    Arguments:

    • top-n: a non-negative number, optional
  • player-pos~ >=Moderator

    Prints the current player position in pixels

  • entity-count:tags~ >=Subscriber

    Count the amount of loaded entities.

    Can be filtered down by tags, for example entity-count:gold_nugget~.

    Per-user timeout: 5s

    Arguments:

    • tags: the rest of the arguments
  • entity-tag-counts~ >=Vip

    Aggregate a top list of tags that mark loaded entities.

    Per-user timeout: 5s

Economy

  • balance~

    Check your current balance of charges

    Per-user timeout: 3s

  • unleash-me~ cost: 1⚡︎

    Spend a charge to remove all of your current timeouts.

  • transfer:target:amount~ cost: 0.004⚡︎

    Transfer some of your charges to another user

    Per-user timeout: 3s

    Arguments:

    • target: a user login
    • amount: a number of charges, in form of a number with up to 3 decimal places

Macros

  • macro-record:name:script~ shortcode: mr

    Stores a string as a personal macro.

    Commands can accept complicated strings if you put them in quotes like so:

    macro-record:hop:"wait~ up~ wait~ up~ wait~ up~ wait~ up~"~

    Arguments:

    • name: string
    • script: a script string that will not have it’s vars expanded
  • macro-delete:name~ shortcode: md

    Deletes a macro created with macro-record~.

    Arguments:

    • name: string
  • global-macro-record:name:script~ >=Moderator shortcode: gmr

    Stores a string as a global macro, meaning it can be used by everyone.

    Arguments:

    • name: string
    • script: a script string that will not have it’s vars expanded
  • global-macro-delete:name~ >=Moderator shortcode: gmd

    Deletes a macro created with global-macro-record~.

    Arguments:

    • name: string
  • macro-print:name:chatter?~ shortcode: mp

    Replies with the stored macro.

    Can peek at other users macros if their login is specified, they’re all public here.

    Per-user timeout: 5s

    Arguments:

    • name: string
    • chatter: a user login, defaults to you
  • global-macro-print:name~ shortcode: gmp

    Replies with the stored global macro.

    Per-user timeout: 5s

    Arguments:

    • name: string
  • macro-list:chatter?~ shortcode: ml

    List macros you/given chatter has recorded.

    Per-user timeout: 5s

    Arguments:

    • chatter: a user login, defaults to you
  • global-macro-list~ shortcode: gml

    List global macros recorded.

    Per-user timeout: 5s

  • yoink:name:chatter?:rename?~

    Copy someones macro to yourself.

    Arguments:

    • name: string
    • chatter: a user login, defaults to you
    • rename: string, optional
  • macro:name:chatter?:rest~ shortcode: q

    Run the macro.

    Macros can call other macros, but there is a recursion limit!

    Also you can run a macro recorded by someone else by appending their login as the second argument.

    Arguments:

    • name: string
    • chatter: a user login, defaults to you
    • rest: the rest of the arguments
  • repeat:times:script~

    Execute a given string several times.

    jump five times: repeat:5:" wait~ up~ "~

    The total limit of repetitions in a given message is 1000! This counts all repetitions, nested in each other or inside of macros etc etc. Once the limit is reached, the command will error out.

    Arguments:

    • times: a number in range from 0 to 1000
    • script: a script string that will not have it’s vars expanded
  • group:script:custom-status-name?~ shortcode: g

    Executes a given string, basically identical to repeat:1:"script".

    This is useful to group together parallel actions, for example:

    wait:5s~ group:" up~ | left~ "~

    Additionally, there is an optional name that you can attach to the group to have it shown on the status wall.

    Arguments:

    • script: a script string
    • custom-status-name: string, optional
  • try:script~

    Similarly to group, executes a given string without counting towards limits.

    The difference is that any script errors are ignored, and this command always succeeds, without preventing the repeats from continuing or setting last-error.

    Arguments:

    • script: a script string
  • lock:script~ shortcode: b

    Similarly to group, executes a given string without counting towards limits.

    The difference is that only one lock script can run at a time, if one is already running this command does nothing.

    Arguments:

    • script: a script string
  • loop:script~ >=Subscriber

    Repeatedly execute a given script indefinitely - until an interrupt is sent.

    A special condition: a single loop iteration must take at least 100ms, otherwise after the first iteration the command will error out.

    Unless you have a repeat inside of the loop that exceeds it, the loop is not limited by the repeat limit.

    The only way to stop a running loop is interrupt~, and putting a loop inside of a loop is obviously pointless.

    stalling: repeat:5:" wait~ up~ "~

    Arguments:

    • script: a script string that will not have it’s vars expanded
  • math:value~

    Evaluates and prints a given math expression. Mostly useful for debugging my calculator bugs :(

    Per-user timeout: 3s

    Arguments:

    • value: a number
  • set:name:value?~

    Stores text that will be replaced in any commands you call (including macros) if you reference it as %name.

    Note that while this command accepts text, an argument wrapped in parenthesis - without quotes or braces - will be passed through the calculator first as if the command expected a number.

    Arguments:

    • name: string
    • value: string, optional
  • let:name:value?~

    Similar to set~, but the text is only stored for the chat message being executed.

    Arguments:

    • name: string
    • value: string, optional
  • del:names~

    Deletes a variable. Can delete more than one at once.

    Arguments:

    • names: the rest of the arguments
  • list-vars~

    Lists all of your variables.

    Per-user timeout: 5s

  • get:name~

    A debug command that replies with the value of the given variable.

    Per-user timeout: 5s

    Arguments:

    • name: string
  • clear~

    Clears all of your variables.

  • if-eq:a:b:then:else?~

    Runs the then script if given values are equal, otherwise runs the else script if given.

    Arguments:

    • a: string
    • b: string
    • then: a script string
    • else: a script string, optional
  • if-ne:a:b:then:else?~

    Runs the then script if given values are not equal, otherwise runs the else script if given.

    Arguments:

    • a: string
    • b: string
    • then: a script string
    • else: a script string, optional
  • if-ge:a:b:then:else?~

    Runs the then script if the first number is greater than or equal to the second, otherwise runs the else script if given.

    Arguments:

    • a: a number
    • b: a number
    • then: a script string
    • else: a script string, optional
  • if-gt:a:b:then:else?~

    Runs the then script if the first number is greater than the second, otherwise runs the else script if given.

    Arguments:

    • a: a number
    • b: a number
    • then: a script string
    • else: a script string, optional
  • if-le:a:b:then:else?~

    Runs the then script if the first number is less than or equal to the second, otherwise runs the else script if given.

    Arguments:

    • a: a number
    • b: a number
    • then: a script string
    • else: a script string, optional
  • if-lt:a:b:then:else?~

    Runs the then script if the first number is less than the second, otherwise runs the else script if given.

    Arguments:

    • a: a number
    • b: a number
    • then: a script string
    • else: a script string, optional

Moderation

  • banish:chatter:duration?~ >=TwitchStaff

    Instantly banish a user to the shadow realm.

    Can be temporary if a duration is provided.

    Arguments:

    • chatter: a user login
    • duration: duration in freeform format (using humantime Rust library), so 10s, or 2 minutes or 1h30m etc, optional
  • unbanish:chatter~ >=Moderator

    Restore users ability to use the bot, bringing them back from the shadow realm regardless of their crimes.

    Arguments:

    • chatter: a user login
  • banished:chatter~

    Check if a user was yeeted into the shadow realm.

    If you are yeeted, the bot ignores you utterly, so this won’t work ¯\_(ツ)_/¯.

    Per-user timeout: 15s

    Arguments:

    • chatter: a user login

Sounds

  • tts:msg~ cost: 0.5⚡︎ / >=Subscriber global macro exempt

    Say something on stream through the TTS.

    Per-user timeout: 1m

    Arguments:

    • msg: string
  • play-sound:sound-id~ >=Caster global macro exempt

    Play a sound on stream. Sounds ids are secret, but look through global-macro-list~ for macros that use this command to get an idea of what sounds are available.

    Sender gate is at least 10 seconds for everything, but individual sounds can have their own dynamic cooldowns (currently all sounds at 1 minute).

    Per-user timeout: 10s

    Arguments:

    • sound-id: string
  • now-playing~ shortcode: np

    Get the title of the song that’s currently playing on stream, if any.

    Global timeout: 5s

  • skip~ cost: 1⚡︎ / >=Vip

    Skips the song that’s currently playing on stream, if any.

    Global timeout: 15s

  • song-request:url-or-id~ shortcode: sr

    Adds the given song to the YouTube Music queue.

    This only accepts YouTube links or IDs.

    Note that the song will be added to the front of the queue - this is because usually the queue is full of songs from my stream playlist and the point of the command is to show me a song you think I wont insta-skip :)

    Per-user timeout: 1m

    Arguments:

    • url-or-id: string
  • volume:volume~ >=Vip

    Set YouTube Music volume.

    Per-user timeout: 3s

    Arguments:

    • volume: a number in range from 0 to 100

Stats

  • stat:chatter?:word?~

    Count the amount of messages typed by a chatter.

    If given a word, counts the messages containing it (given string can give multiple words separated by spaces).

    Login defaults to the sender (you can do stat::word~ too).

    Per-user timeout: 3s

    Arguments:

    • chatter: a user login, defaults to you
    • word: string, optional
  • stat-global:word?~

    Similar to stat except works across all of chat.

    Without an argument counts all messages ever typed in chat since I started archiving it (which is way before the Twitch Plays Noita happened).

    With an argument filters messages by the given word(s), just like stat.

    Per-user timeout: 3s

    Arguments:

    • word: string, optional
  • first-message:chatter?~

    Get the first message sent by a user (or you) in chat.

    Per-user timeout: 3s

    Arguments:

    • chatter: a user login, defaults to you
  • last-message:chatter?~

    Get the last message sent by a user (or you) in chat.

    Per-user timeout: 3s

    Arguments:

    • chatter: a user login, defaults to you
  • top:n?~

    Get a list of top-N chatters of all time, by number of sent messages.

    Per-user timeout: 10m

    Arguments:

    • n: a number in range from 1 to 15, optional
  • rank:chatter?~

    Get the place of the chatter (or you) in the “leaderboard” of how many messages they spammed sent

    Per-user timeout: 1m

    Arguments:

    • chatter: a user login, defaults to you

Util

  • ping~ cost: -1⚡︎

    Respond with “pong!”.

    I heard that scarcity creates value, so getting a pong is very cool and pog, because only one person can get it in an hour.

    There is also some magical property to this command..

    Global timeout: 1h

  • echo:text~ >=Caster global macro exempt

    A building block for basic static text commands.

    This just makes the bot print the given text, but non-moderators can only call it through global macros.

    So you can call a global macro discord~ which will resolve to echo:"discord link etc"~ and print it.

    Per-user timeout: 5s

    Arguments:

    • text: string
  • last-error:chatter?~

    Respond with the last error message for user.

    Last error message is set when you run invalid commands, or if the command execution managed to crash somehow. In the latter case, you’ll be given the message id - please send it to me to look at logs and fix the issue.

    Per-user timeout: 3s

    Arguments:

    • chatter: a user login, defaults to you
  • is-game-running~

    Check if noita.exe process is present, aka not dead.

    Per-user timeout: 1m

  • wait:duration?~ shortcode: w

    Wait for a specified duration milliseconds.

    Very useful for multi-command messages.

    Arguments:

    • duration: duration in milliseconds, will be limited to at most 300000, defaults to 500
  • break:chatter?~

    Complete all current holds immediately.

    “Holds” here are referring to all “non-instantaneous” commands that are currently being executed right now - so movement, hold~ and wait~.

    This is kind of a niche thing, most likely you need interrupt~.

    Arguments:

    • chatter: a user login, optional
  • interrupt:chatter?~

    Stop running all current commands.

    This is similar to break~, except the commands following the holds that get completed do not run.

    Arguments:

    • chatter: a user login, optional
  • discard~

    Interrupt all commands originating from the current message.

    On it’s own this does nothing, but it can be used to limit the duration of the current message or similar.

  • flag:flag~ >=Moderator

    Set a bot flag.

    Flags that currently do things are:

    • full-stop: disables processing any commands from non-mods
    • no-restarts: disables the game restarting on player death
    • nightmare: enables the nightmare mode

    If the flag argument is prefixed with - it is removed if it was set previously.

    Arguments:

    • flag: string
  • set-nightmare:value~ >=Vip

    Makes the bot start the game in nightmare/ng.

    Arguments:

    • value: a boolean value (true/t/yes/y/1 or false/f/no/n/0)
  • fix-seed:seed~ >=Vip

    Makes the bot start the game with a specific seed.

    Arguments:

    • seed: a non-negative number
  • unfix-seed~ >=Vip

    Undoes the effect of fix_seed~, so the game will start with a random seed again.

  • what-is:name:to?~

    Get information about a macro or command.

    If you see someone running some weird command, run what-is:command:their-name~ to figure out what it was.

    Per-user timeout: 3s

    Arguments:

    • name: string
    • to: a user login, defaults to you

Voting

  • yes~

    Vote yes in an ongoing vote.

  • no~

    Vote no in an ongoing vote.

  • is-vote~

    Check if there is an ongoing vote and what it is about.

    Global timeout: 10s

  • votekick:chatter~

    If several(!) people run this command with the same Twitch login (this is important, use their login, not display name) as an argument - this will start a vote to send that user to the shadow realm.

    The vote has to have >=70% yes votes for them to get yeeted.

    By “shadow realm” I mean that their commands will be ignored - initially for an hour, but if they get voted for the second time then forever, unless a moderator clears them.

    This is obviously to deal with trolls and other problematic users. Channel moderators and above are immune.

    Be aware that there can be only one vote at a time and this command has a large per-user cooldown, so dont waste it.

    Per-user timeout: 5m

    Arguments:

    • chatter: a user login

Last modified 2025-08-05 15:43:21 via
ec40e6c tpn: fix some typos and sync docs