Unified Commands API
Description:
The Commands API is a Bash Script-like execution environment for Minetest entities.
There are the following fundamental constructs:
commands
: execute a specific action (e.g. dig a node, search for nodes, etc.)
variables
: stores information temporarily (e.g. results of node search)
control statements
: if/else and loops
All of these is done using the Lua programming language, and a few custom
expressions where Lua can't help.
The fundamental concept is to use the three constructs together in the form of
a script to allow NPCs (and any Minetest entity) to perform complex actions,
like walking from one place to the other, operating furnaces, etc. In this sense,
this "commands" API can be considered a domain-specific language (DSL), that is
defined using Lua language structures.
Basic definitions:
A variable
is any value that can be accessed using a specific key, or name.
In the context of the commands API, there is an execution
context where
variables can be stored into, read and deleted from. The execution context
is nothing but a map of key-value pairs, with the key being the variable name.
Some rules regarding variables:
- A variable can be read-write or read-only. Read-only variables cannot be
updated, but can be deleted.
- A variable cannot be overwritten by another variable of the same name.
- The scope of variables is global within a script. The execution context
is cleared after a script finishes executing. For more info about scripts,
see below.
- The Lua entity variables, referring to any
self.*
value isn't available
as a variable. This is to keep the NPC integrity from a security perspective.
However, as some values are very useful and often needed (such as
self.object:getpos()
), some variables are exposed. These are referred to
as internal variables. They are read-only.
A 'command' is a Lua function, with only two parameters: self
, the Lua entity,
and args
, a Lua table containing all arguments required for the command. The
control statements (if/else, loop) and variable set/read are defined as commands
as well. The arguments are not strictly controlled, with the following exceptions:
- A
variable expression string
is special string that allows a variable
to be passed as an argument to a command. The reason why a function can't
be used for this is because the execution context, where variables are
stored, lives in the entity itself (the self
object), to which there's no
access when a script is defined as a Lua array.
The special string has a specific format: "<var_type>:<var_key>"
where the
accepted values for <var_type>
are:
var
, referring to a variable from the execution context, and,
ivar
, referring to an internal variable (an exposed self.* variable)
- A
function expression table
is a Lua table that contains a executable
Lua function and the arguments to be executed. The function is executed
at the proper moment when passed as an argument to a command, instead of
executing immediately while defining a script.
The function expression table has the following format:
{
func = <Lua function>,
args = <Lua table of arguments for the function>
}
- A
boolean expression table
is a Lua table that is reconstructed into a
Lua boolean expression. The reason for this to exist is similar to the
above explanation, and is that, at the moment a script is defined as a
Lua array, any function or variable passed as a boolean expression will
evaluate, making the value effectively a constant. That would render
loops and if/else statements useless.
The boolean expression table has the following format:
{
left_side: <any_value|function expression table|variable expression string>,
operator: <string>,
right_side: <any value|function expression table|variable expression string>,
}
operator
and right_side
are optional: a single function in left_side
is
enough as long as it evaluates to a boolean
value. The operator
argument
accepts the following values:
- equals
- not_equals
- greater_than
- greater_than_equals
- less_than
- less_than_equals
right_side
is required if operator
is defined.
A script
is an ordered sequence of commands to be executed, and is defined
using a Lua array, where each element is a Lua function corresponding to a
command. Scripts are intended to be implemented by users of the API, and as
such it is possible to register a script for other mods to use. For example,
a script can be used by a mod that creates a music player node so that NPCs
can also be able to use it.
Scripts can also be executed at certain times during a Minetest day thanks
to the schedules functionality.
Execution:
The execution of commands is performed on a timer basis, per NPC, with a default
interval value of one second. This interval can be changed by a command itself,
however it is the recommended interval is one second to avoid lag caused by many NPCs
executing commands.
Commands has to be enqueued in order to execute. Enqueuing commands directly isn't
recommended, and instead it should be done through a script. Nonetheless, the API
for enqueuing commands and scripts is the following:
- npc.enqueue_command(command_name, args)
- npc.enqueue_script(script_name, args)
The control statement commands (if/else, loops) and variable set/read commands
will execute the next command in queue immediately after finishing instead of
waiting for the timer interval.
There is an execution context
which contains all the variables that are defined
using the variable set commands. Also, it contains values specific to the loops,
like the number of times it has executed. The execution context lives in the NPC
self
object, and therefore, has to be carefully used, or otherwise it can create
huge memory usage. In order to avoid this, variables can be deleted from the execution
context using a specific command (npc.commands.del_var(key)
). Also, as basic
memory management routine, the execution context
is cleared after the end of
executing a script.
To keep global variables, use the npc.command.get/set_flag() API which is not
deleted after execution.
New commands
The following commands will be added to the default set:
do_punch
: Executes the on_punch
function of a node, object or player
do_rightclick
: Executes the on_rightclick
function of a node, object or player
set_property
: Sets the value of a variable in the self.properties
object. If the variable doesn't exists, it is created. This command is executed immediately and is not enqueued.
- Parameters:
key
: The property key-name. This is a variable in the self.properties
object
value
: The property value.
get_property
: Returns the value of a given property. This command is executed immediately and is not enqueued.
- Parameters:
key
: The property key-name.
set_internal_property
: Sets the value of a limited set of internal properties related to the NPC trading and personality variables.
get_internal_property
: Gets the value of a limited set of internal properties related to the NPC trading and personality variables.
add_item_to_npc
: Adds an item to the NPC inventory, without any specific source.
remove_item_from_npc
: Removes a specific item from the NPC inventory.
query
: Executes a query for nodes or objects. Returns a Lua table with none, single or many positions.
If/else:
This command allows the conditional execution of two array of commands
depending on the evaluation of a certain condition. This is the typical
if-else statement of a programming language. If-else can be nested.
Arguments:
condition
: accepts two values:
boolean
: Lua boolean expression, any expression that evaluates to true
or false
.
table
: A boolean expression table.
true_commands
: an array of commands to be executed when the condition
evaluates to true
false_commands
: an array of commands to be executed when the condition
evaluates to false
Loops:
This command works as the types of loops, depending on the arguments
given. It can work as a while, a for and a for-each loop. Loops can
be nested.
While-loop arguments:
name
: string, a key-name for this loop. Default is nil. If given,
it gives access to the number of times the loop has executed.
condition
: boolean, the loop will be executed as long as this condition
evaluates to true
commands
: array, array of commands to be executed during the loop
For-loop arguments:
name
: string, a key-name for this loop. Default is nil
. If given, it
gives access to the number of times this loop has executed.
initial_value
: integer, the starting value of the for-loop. If left
blank, default value is 1
.
condition
: boolean, the loop will be executed as long as this condition
evaluates to true
.
modifier
: function, the loop will execute this modifier at the end of
every iteration. If left blank, default is: initial_value + 1
commands
: array, array of commands to be executed during the loop
Both of these loops store how many times they have been executed. To
access it, it is required to give pass the argument name
. Then the
value will be stored on the execution context and the value retrievable
with npc.commands.get_context(key)
, where key
is the name
argument.
For-each-loop arguments:
name
: string, a key-name for this loop. Default is nil
. If given, it
gives access to the number of times this loop has executed and the current
value of the array/table being evaluated.
iterable
: array or table of key-value pairs, this is an iterable array
or table for which the loop will execute commands at every element in the
iterable array/table.
commands
: array, array of commands to be executed during the loop
To get the current element being iterated in a for-each loop, you need to define
the name
argument. Then, the value will be stored in the execution context and
will be retrievable with npc.commands.get_context(key)
. It will return a table
like this: {loop_count = x, current_value = y}
Extensibility
Once the above commands has been added, it is possible to safely build scripts which don't touch directly many of the internal NPC mechanisms. An API will be provided for external mods to register scripts that let NPCs perform actions related to those mods, e.g. operating a node provided by the mod. The API for this will be:
npc.commands.register_script(name, script)
All registered scripts have the following properties:
- They are interruptable by the command queue/scheduler
- They are not immediately executed
The script
parameter is a Lua array of commands that will be executed when the script is executed.