Go to file
2025-04-10 09:36:42 +03:00
.luacheckrc add some useless bytes 2025-04-05 16:35:49 +03:00
dump.lua be more consistent and less confusing with dumped object names 2025-03-29 16:23:16 +03:00
init.lua ascii() and unascii() to help with broken utf-8 or binary data 2025-04-10 09:36:42 +03:00
LICENSE add dead bytes 2025-03-19 10:28:32 +03:00
mod.conf add some useless bytes 2025-04-05 16:35:49 +03:00
README.md ascii() and unascii() to help with broken utf-8 or binary data 2025-04-10 09:36:42 +03:00
todo.org some quality of life features for using global vars 2025-03-20 12:43:45 +03:00
util.lua ascii() and unascii() to help with broken utf-8 or binary data 2025-04-10 09:36:42 +03:00

Adds /eval command

/eval takes lua code as argument and executes it. It will echo your command and show it's output and returned value. Each player gets their own "global" environment so they can't interfere with other user's envs by accident (exposed as cmd_eval.e[player_name]).

Type /eval help to see the built-in help.

Some nice features:

Expression/statement agnostic

This command:

/eval 1+2

Outputs this:

> 1+2
| 3

This also just works:

> x = 2*2

> x
| 4

Multiple values also work:

> return 1,nil,3
| 1,
| nil,
| 3

Backtrace and error output

Outputs both the error and clean backtrace (stack related only to provided code)

Print function

No need to use core.chat_send_player(), just use print() - it will do the right thing.

> print(here)
< (-98.0, 15.5, 33.4)
> objs = core.get_objects_inside_radius(here, 10)

> for i,o in ipairs(objs) do print(i, (o:get_luaentity() or {}).name, o:get_pos()) end
< 1 nil (-98.0, 15.5, 33.4)
< 2 mobs_animal:kitten (-101.0, 16.5, 30.0)
< 3 mobs_animal:chicken (-92.0, 15.5, 34.2)

"Magic" variables

Some special variables are provided:

  • here - position where you executed the command
  • me - your player object
  • point - point in the world you're pointing at with your crosshair
  • this_obj - entity you're pointing at (can be nil)

Do /eval help to get a quick reminder of these vars and functions.

Better output for arrays and some userdata objects

> me
| #<player: "singleplayer">

Show indices for easier manual access:

> core.get_objects_inside_radius(here, 10)
| {
|  [1] = #<player: "singleplayer">,
|  [2] = #<luaentity: "mobs_animal:kitten">,
|  [3] = #<luaentity: "mobs_animal:chicken">,
| }

Keeping result of last /eval in _ variable

> core.get_objects_inside_radius(here, 10)
| {
|  [1] = #<player: "singleplayer">,
|  [2] = #<luaentity: "mobs_animal:kitten">,
| }

> _[2]
| #<luaentity: "mobs_animal:kitten">

> pos = _:get_pos()

> pos
| {
|  x = 123.5,
|  y = 15.0,
|  z = 68.4
| }

Accessing and changing global variables

You can save some keypresses by not typing local in front of every variable. Assigning to globals won't clobber them, instead using your personal environment. But you can still read globals as usual.

> print(myvar)
* Accessing undeclared variable: "myvar"
< nil

> myvar = 1
| Done.

> print(myvar)
< 1
| Done.

If you want to create a new global or actually overwrite existing one, you can access global environment through usual _G or global variables. Using them will print a message telling you if global var already existed.

> cmd_eval = nil
| Done.

> cmd_eval  -- accesses real global
{
  e = { ... }, ...
}

> global.cmd_eval = nil
* Overwriting global: "cmd_eval"
| Done.

> cmd_eval
| nil       -- wiped for real

Other shortcuts

dir() and keys()

List keys of the table (useful for exploring data structures, without flooding your chat).

get_objects_inside_radius() shortcuts: goir() and oir()

goir(radius) returns a list of objects around you oir(radius) returns an iterator of objects around you

> goir(100)   -- return a list of objects within 100 units around you

> for v in oir(100) do print((v:get_luaentity() or {}).name) end

Coroutine support

You can call yield(...) inside the code you're evaluating. The yielded value will be displayed, same as normal returned value, but you will also be able to resume the computation by typing /eval_resume:

> for i=1,3 do yield(i) end
| 1
* coroutine suspended, type /eval_resume to continue
/eval_resume
* resuming...
| 2
* coroutine suspended, type /eval_resume to continue
/eval_resume
* resuming...
| 3
* coroutine suspended, type /eval_resume to continue
/eval_resume
| Done.
/eval_resume
* resuming...
* Nothing to resume

This can allow you for some shortcuts, for example, visiting all players:

/eval for p in players do me:move_to(p:get_pos()); yield() end

type /eval_resume to visit next one.

Keep in mind that while the coroutine is paused, it's enviroment can change (tables can be modified, some object refs can become invalid etc.)

An example of using yield()

Evaluate this while pointing at the first position:

/eval a = point; yield(); b = point; vizlib.draw_line(a,b); print(vector.distance(a,b))

Then point at the second position, and type /eval_resume. This will draw a line between 2 points and print the distance between them.

Formspec output/input

fsdump(value)

Evaluate fsdump(value) to show the value in a formspec instead of chat window. This will allow you to select and copy the dumped text, and also keep your chat history from getting spammed. You can call this multiple times, inside a loop, etc. The computation will be paused (see "coroutine support").

If you close the dump window with ESC or x button, the computation will remain paused, and can be resumed by typing /eval_resume. If you push resume formspec button, it will be resumed normally.

Example using fsdump()

Inspecting lots of objects around you by visiting and dumping them:

/eval for o in oir(30) do the_obj = o; me:move_to(o:get_pos()); fsdump(o:get_luaentity()); end

This command will teleport you to position of some entity nearby and open a formspec with the dump of it's lua table. After examining the values, you can press the "resume" formspec button to resume the evaluation and go to the next entity. We stored current entity in a variable, so we can reference it when we paused. If you just closed the formspec instead of pressing "resume", you can sill continue execution by typing /eval_resume.

NOTE: cmd_eval currently supports only single coroutine, for simplicity reasons; this means that executing /eval while paused, will overwrite your current coroutine with a new one.

fsinput(label, text)

Evaluate tfsinput() (arguments are optional) to open a formspec where you can input some text. The text you enter will be passed back to the coroutine as a return value of the fsinput() call.

For example:

/eval core.get_meta(under):set_string('infotext',fsinput())

Will open a window that will let you to edit the infotext meta field of the node you're pointing at.

Or, same thing, but fancier, showing existing infotext in a formspec:

/eval local m = core.get_meta(under); m:set_string('infotext',fsinput('infotext', m:get_string('infotext')))

If you close fsinput() formspec without pusing send button, the computation will not be resumed.

You can still resume it by typing /eval_resume <text> - the argument text will be passed instead of the context of text area of the formspec.

More utilities

Binary data

ascii() and unascii()

Have you ever encountered "" somewhere with no easy way to display it?

ascii() will allow you to inspect these strings (which may be just some broken unicode or arbitrary binary data).

ascii(str) will escape all control sequences and unprintable characters so you can output it normally. It works well with broken strings, because printable ASCII characters will be kept unchanged.

The reverse of this is unascii(str), which will take the escaped sequence and turn it back into raw bytes. This supports only \xFF hex escapes, but it works well enough in pair with ascii().

Resetting your personal environment

If your environment gets messed up, or you just want to get rid of the variables you've stored, you can type:

/eval_reset