Help Guide

This is meant to be a supplement to the internal help. There is much that is not explained or is assumed that once learned is best documented to be shared. This document is structured based on how it all fell out of my head. There wasn't any real planning to it. If someone wants to come along and sort it out or expand on any of it, that would be wonderful.

The basics

Hardcode versus Lua

The built-in game commands are frequently referred to as hardcode, whereas any user-create commands or scripts are often referred to as softcode. MAGA uses Lua for softcode. You may find the two terms used interchangeably in this guide, or even referred to as the parser in some instances.

The parser

When running hardcode commands, you can invoke the Lua parser by enclosing code within square brackets: []. For example: @print [print(me.Name)]. There are a few things to note in that example. The first is that if you want any output from the parser, you need to invoke the print() function, or another function that produces output. Another is the me object, this is a reference to the MAGA database object that is running the code, in this case you. me.Name refers to the Name attribute of me, where Name is one of the special 'non-attribute attributes'. More on that in a moment.

Pseudo attributes

There are several specific points of data belonging to an object that aren't stored in attributes. When using Lua, these can be retrieved as if they were attributes, even though they are not. For example, you could print(me.Name) when an objects name is in fact not stored in any attribute. Here is a list of the pseudo attributes:

Name Returns
Alias Object's alias
Id Numeric portion of object's database ID
Location Numeric portion of object's location database ID
Name Object's name
Owner Numeric portion of object's owner's database ID
Type Object type
You might notice that all of these start with a capital letter. That is to avoid conflict with any user-created attributes, which are suggested to start with lowercase letters. This brings another important point, all attribute names are case sensitive. You can @define FooBar /str on me and @define Foobar/str on me and you will end up with two attributes defined, one named FooBar and one named Foobar. You can use this to your advantage if you had an object that needed to store a string that represented a location of some sort, you could use an attribute named location without it conflicting with Location, however it will probably just lead to confusion in the end.

Objects

The parser creates a couple of special Lua objects, such as me, which you've already seen mention of.

Object Purpose
me A reference to the database object that is currently running the Lua code
here A reference to the database object of the location of the object that is currently running the Lua code
(Whew, that last one was a mouth-full)

If you want to retrieve a specific object, take a look at the thing() function.

Sandboxing

It's not the wisest thing to turn a server full of players loose with access to every Lua function and library running under the same permissions as the server user. You'd invariable have someone invoking os.remove() and deleting something they shouldn't, or causing some other sort of mayhem you haven't thought of yet. As a result, a couple of security measures are in place. All of the Lua code runs in a per-user sandbox. One user's code cannot affect another's. In addition, only specific Lua functions and libraries have been exposed. At present the list of functions is not available from the built-in help, but I've managed to squeeze them out of TheFool76, so here they are:

Function Function FunctionSorted ascending
os.clock() setmetatable()  
assert() os.date() string.*
error() os.difftime() table.*
ipairs() os.time() tonumber()
math.* pairs() tostring()
next() select() type()

MySQL

MAGA provides a player MySQL database for those with the SQL power. It can be accessed either via the @sql command or the sql() function. It is not necessary to use a trailing semicolon (;) for single SQL statements.

Here are some (very) random examples pulled from some incomplete projects:

*Examples:*

@sql SELECT * FROM coords_20

Success!
+-------+----+---+
| objid | x  | y |
+-------+----+---+
| 5     | 10 | 0 |
+-------+----+---+

@lua sql("UPDATE SolarSystems SET time=" .. os.time() .. " WHERE objid=2")
@lua myship = sql("SELECT * FROM SolarSystems WHERE objid=2") if myship[0] ~= nil then print(myship[0].lon) end

280

The sql() function will always at least return an array with element 0. If there are no results to your query than element 0 will be nil. If there are results, there will be one or more numbered elements, starting at 0. Each will have named elements matching the MySQL column names. In the last example, I had a column named lon (representing longitude in relation to the system's primary star). Because I had at least one result (only one in this case), element 0 (myship[0]) was not nil, and had an element named lon accessible as myship[0].lon.

Spreading things out

Code gets hard to manage in a hurry when it's all on one line. One way to deal with this is to spread it across multiple lines. Any time the MAGA sees your input end in a single backslash (\), it will assume you intended to enter a newline in the current input without terminating the input. For example, we could really make a mess of channels:

+public This is my first line\
and this is my second
[public] QBFreak: This is my first line
and this is my second

What you can't tell from this monochrome, frozen-in-time example is that the command didn't send until after I hit enter at the end of the second line. In addition, both output lines are formatted with my colors for channel output, not just the first. The MAGA interpreted the \ as a newline in the current command, not a request to execute the command as thus far entered. You can use this to your advantage when entering multiple lines of code:

@lua \
   if tonumber(os.date("%H")) < 12 then \
      print("Good morning!") \
   end

That was much easier to read than @lua if tonumber(os.date("%H")) < 12 then print("Good morning!") end, although a lot harder to go back and make a quick edit and run again. I make use of this MAGA feature in combination with my client's ability to send files as input. This way I can contain all of my code for a particular object or project in a single file and send it all at once. I can edit to my hearts content in an editor that's much friendlier than my client's input line. I can save a local copy which is really nice to have when you make a neat object and want it on multiple servers. Best of all, if I want to experiment and need to make sure I can undo it all, I just have to save a copy of the file before I start making my changes. The really neat thing about sending multi-line functions (or any other attribute value), is they are actually stored this way. If you @ex the object, you'll see your data, spread across as many lines as you choose to enter it. This is really nice if you need to quick check some code with @ex.

@set foobar/str on me to this\
is\
a\
test

Attribute defined and set.

@ex me

Examining QBFreak (#5)
Attributes
foobar/STRING: this
is
a
test

Advanced MAGA magic

Queue_cmd()

The queue_cmd() function allows you to add MAGA commands to the command queue from Lua. This means you can run MAGA-specific commands (@copy for instance) from inside a Lua function. In addition to this, you can specify which object runs the command, how long to wait until it runs, and even a string to identify the command. This one function encompasses the functionality of several TinyMARE commands. The help for queue_cmd() is a little light on details, but it will give you the syntax:

queue_cmd( <command>, [<#id>], [<wait_time>], [<timer_id>] )

The most basic usage is to immediately run a command:
@lua queue_cmd("@print Hello, World!")
Hello, World!

The next is to make some other object run a command:
@lua queue_cmd("say Hello, World!",thing("w").Id)
West says, "Hello, World!"

This of course assumes that there is an exit aliased "w" in the same room as you, and that you own it (or otherwise have the power to make it run commands). If you don't own anything just replace thing("w").Id with me.Id to make yourself do it.

Probably one of the most common uses of this function, is to run a command after a specific amount of time has elapsed:
@lua queue_cmd("say First command", me.Id, 3000)
say Second command
You say, "Second command"
You say, "First command"

It is important to note that <wait_time> is in milliseconds. If you want seconds, you'll want to multiply your value by 1000. In my example above, I waited 3 seconds or 3000 milliseconds. This usage of queue_cmd() is quite useful for delaying responses in things like NPCs.

The last usage and probably the most powerful allows you to assign a <timer_id> string to your command. If you do this, you can call queue_cmd() again before the timer has expired and change the timer or the command. Going into details of how this is useful is beyond the scope of this document (there are in fact just too many ways), but I will give you an example of how it works:

@lua queue_cmd("say Whew, that was a long wait", me.Id, 10 * 60 * 1000, "mycommand")
@lua queue_cmd("say Well that wasn't so bad", me.Id, 10 * 1000, "mycommand")
You say, "Well that wasn't so bad"

The first command we queued would have waited 10 minutes (1000ms = 1 second * 60 = 1 minute * 10 = 10 minutes) before it ran. The second one, replaced the first in the command queue with not only a new command, but also a new wait time.

-- SolarQBFreak - 05 Apr 2017
Topic revision: r5 - 07 Apr 2017, SolarQBFreak
This site is powered by FoswikiCopyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding WikiMARE? Send feedback