TPN Script Reference
TPN script is a “scripting language” of sorts that I am using in my Twitch chat.
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 thatsilent: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~
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.
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 😉
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.
Betting
-
mkbet:premise:options~ >=Caster
Create a new bet. If no options are provided, they default to “believe” and “doubt”.
Arguments:
- premise: string
- options: the rest of the arguments
-
close~ >=Caster
Close the current bet, preventing new bets from being placed.
-
reopen~ >=Caster
Reopen a closed bet, allowing new bets to be placed.
-
is-bet~
Show the current active bet.
Per-user timeout: 10s
-
bet:option:wager?~
Place a bet on the current active bet.
If you dont wager anything, you will still win 1⚡︎ if you were correct.
Per-user timeout: 5s
Arguments:
- option: string
- wager: a number of charges, in form of a number with up to 3 decimal places, optional
-
get-bet:chatter?~
Get your current bet, if any.
Per-user timeout: 5s
Arguments:
- chatter: a user login, defaults to you
-
unbet~
Remove your bet from the current active bet, refunding your wager.
Per-user timeout: 5s
-
cancel-bet~ >=Caster
Cancel the current bet, refunding all wagers.
-
rollback~ >=Caster
Rollback the last settled bet, returning all winnings to the users and restoring the bet state.
This is obviously to fix potential fat-finger mistakes.
-
settle:option~ >=Caster
Settle the current bet, paying out the users who bet on the given option.
Arguments:
- option: string
Data
-
seed~
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~ >=Subscriber
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
-
player-pos~ >=Subscriber
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~ >=Subscriber
Aggregate a top list of tags that mark loaded entities.
Per-user timeout: 5s
Economy
-
balance:chatter?~ shortcode: b
Check the current charge balance
Per-user timeout: 3s
Arguments:
- chatter: a user login, defaults to you
-
unleash-me~ cost: 1⚡︎
Spend a charge to remove all of your current timeouts.
-
transfer:target:amount~ cost: 0.004⚡︎ global macro exempt
Transfer some of your charges to another user.
When run from global macros, the amount can be negative to “steal” charges. This can be used by mods to set up macros that reward users.
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
-
award:target?:amount~ >=Caster
Conjure some charges out of thin air and award them to a user.
The amount can be negative 🙃
Arguments:
- target: a user login, defaults to you
- amount: a number of charges, in form of a number with up to 3 decimal places
-
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-ish.
This command has a dynamic global gate of 55-65 minutes, chosen at random.
Ping fails if you were the last person to do it!
There is also some magical property to this command..
-
last-pinger~
Get the name of the last person who got the
ping~command during the current stream.Per-user timeout: 15s
-
hello~ shortcode: hi cost: -0.2⚡︎
Say hi to the stream!
Per-user timeout: 12h
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:catch?~
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.
The
catchscript, if given, is executed only if the main script errors out.Arguments:
- script: a script string
- catch: a script string, optional
-
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
-
global-set:name:value?~ >=Moderator
A moderator-only version of
set~that sets a global variable instead of a personal one.Global variables will be replaced by anyone referencing them as
%name, unless they have their own personal variable with the same name, which would take precedence.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
thenscript if given values are equal, otherwise runs theelsescript if given.Arguments:
- a: string
- b: string
- then: a script string
- else: a script string, optional
-
if-ne:a:b:then:else?~
Runs the
thenscript if given values are not equal, otherwise runs theelsescript if given.Arguments:
- a: string
- b: string
- then: a script string
- else: a script string, optional
-
if-ge:a:b:then:else?~
Runs the
thenscript if the first number is greater than or equal to the second, otherwise runs theelsescript 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
thenscript if the first number is greater than the second, otherwise runs theelsescript 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
thenscript if the first number is less than or equal to the second, otherwise runs theelsescript 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
thenscript if the first number is less than the second, otherwise runs theelsescript 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
humantimeRust library), so10s, or2 minutesor1h30metc, 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
-
set-title:title~ >=Moderator
Set the stream title, common moderation command, nothing special here.
Global timeout: 5s
Arguments:
- title: string
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 have their own dynamic cooldowns.
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
-
volume:volume?~ >=Vip shortcode: v
Get or set the YouTube Music volume.
Per-user timeout: 3s
Arguments:
- volume: a number in range from 0 to 100, optional
-
skip~ cost: 1⚡︎ / >=Vip
Skips the song that’s currently playing on stream, if any.
Global timeout: 15s
-
unskip~ cost: 1⚡︎ / >=Vip
A “back” button, undoes skips or otherwise goes back to the previous song.
Global timeout: 15s
-
pause:state?~ >=Moderator
Pause/unpause the music.
Arguments:
- state: a boolean value (true/t/yes/y/1 or false/f/no/n/0), optional
-
song-request:url-or-id:extra?~ 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 :)
The
extraparameter is used to allow specifying full URLs without quotes. For example,sr:https://youtu.be/dQw4w9WgXcQ<- here the first argument is actually"https"and theextrais"//youtu.be/dQw4w9WgXcQ".Per-user timeout: 1m
Arguments:
- url-or-id: string
- extra: string, optional
-
cancel-request~ shortcode: cr
Removes the last song you requested from the queue.
Per-user timeout: 5s
-
music-queue:top?~ shortcode: mq
List the songs that were requested through
song-request~.Global timeout: 30s
Arguments:
- top: a non-negative number, optional
-
clear-music-queue~ >=Moderator shortcode: cmq
A helper command to help fix potential music queue issues.
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
statexcept 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
spammedsentPer-user timeout: 1m
Arguments:
- chatter: a user login, defaults to you
-
command-stat:name:chatter?~
Get the amount of times you or some other chatter has successfully(!) used the given command (does not work with shortcodes, use the full command name).
Note that the accurate counting only started since the introduction of this command (with ping~ manually backfilled from chat logs of bot replies).
Per-user timeout: 15s
Arguments:
- name: string
- chatter: a user login, defaults to you
Util
-
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 toecho:"discord link etc"~and print it.Per-user timeout: 5s
Arguments:
- text: string
-
last-error:chatter?~ shortcode: le
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~andwait~.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.
-
setting:key:value?~ >=Moderator
Set a bot setting.
Settings that currently do things are:
full-stop: disables processing any commands from non-modsnosr: disables song requests
If the key argument is prefixed with
-the setting is removed (value is ignored).Arguments:
- key: string
- value: string, optional
-
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