TrueGRF uses a custom (RPN-based) language.
RPN (See: https://en.wikipedia.org/wiki/Reverse_Polish_notation), also known as postfix language or stack-based language, is used as NewGRFs have a limit subsets of supported operations. RPN best matches NewGRFs while still being a slightly higher language than NFO is.
As NewGRF is integer-based, so is this language. This is not so much a rule of this language, more a consequence of the rule NewGRF created.
Variables are normally local scoped (read: uses a temporary register). There is no function-scoping; you can set a variable in one function, and read it in the next.
There are a few namespaces to change the scope of a variable:
industry:
- get variables from the current industtry.town:
- get variables from the related town.func:
- function storage (action2 set-ids).cb:
- like functions, but specific for callbacks.ctt:
- get the id of a cargo label (from the Cargo Translation Table).
result
and for some callbacks result:
are special, to indicate the result value of a cb:
.
Local variables and industry:storage:
are dynamically assigned to a register slot.
That is to say, as soon as you use a new local variable or a new industry:storage:
variable (for example industry:storage:myvariable
) it is automatically assigned the next free register slot.
You do not have to worry about this, and this is all handled for you.
Dictionaries are suported, and the keys and values are always integers.
The []
operator looks up the index from the dictionary.
mylist 2 [] 0 =
Adds the key "2" to the list "mylist" with the value "0".
If you use a dictionary in a loop, it will iterate the keys. You can ge the value by using it as key on the dictionary again:
key mylist loop{
value mylist key [] =
}
Functions never return anything. Use a local variable if you want to talk between functions.
The body is created by using def{
and is closed with a }
.
Loops only work on static information; in other words, they work on anything that can be done before the GRF is actually created.
To help with loops, there is a "range" operator, that creates a list of a range. It comes in four variants: (..]
, (..)
, [..)
or [..]
.
The first and last characters indicate if that side should be inclusive [
/ ]
or exclusive (
/ )
.
For example:
1 10 (..)
returns a list with the elements 2, 3, 4, 5, 6, 7, 8, 9.
A loop begins with a variable to change for each iteration, and is followed by a body to iterate over. For example:
i 1 10 [..] loop{
/* do something with i here. */
}
If-statements work in a simple way: the first item on the stack is the "true" value, the second is the "false" value. Example:
10
11
1 2 >
Means: if (1 > 2) 10 else 11.
All newlines and any spaces more than one are fluff, and meant to increase readability.
They have absolutely no meaning what-so-ever from a language point of view.
This is also the reason //
comments are not allowed.
/* */
comments however are.
To ensure we are using the same persistent registers every time, order of variable definition is important. Additionally, NewGRFs have both signed and unsigned operations. To solve both issues, we need to define each used variable in a type block first.
Changing the order in this list will most likely break savegames that upgrade your NewGRF (read: don't change the order after initial release). If you want to remove a variable, just add a dummy one in return.
There are several types of type-blocks:
const
: doesn't consume registers, and all need to resolve on compile-time. These are "write-once" variables, as in: you can write the const to it once.local
: variables that can be used locally, and lose their value every time a new callback comes in.industry:storage
: variables that need to be stored in the persistent industry storage.town:storage
: variables that need to be stored in the persistent town storage of the town related to the current industry.
There are several types available:
dict
: [const only] a key->value storage.cargodict
: a key->value storage, where the key is cargo. This consumes 64 registers if used inNNN:storage
.list
: a value list. Last item on stack indicates the length of the list.integer
: a value.iterator
: [local only] used as variable for loops.
Examples:
const type{
const_dict dict
}
local type{
local_list 12 list
local_variable integer
i iterator
}
industry:storage type{
persistent_cargo_dict cargodict
persistent_variable integer
}