This document was written for people who intend to write and develop scripts for scripter. If you're just interested in using scripts, then you can read the readme. The readme also contains an overview of WTF scripter is, so you should definitely read it before reading this.
Lua is a lightweight scripting language. For more information about Lua, visit the homepage or the wikipedia page. You might also want to check out the Lua Wiki. Just in case you didn't know, don't edit LUA with notepad. Syntax highlighting is sex. Just google for "LUA syntax highlighting" or "LUA IDE." I personally use VIM, but it's not particularly user friendly. This plugin uses Lua version 5.1. If you want to get started writing plugins quickly, then I recommend you skip the documentation for packet definitions and packages, as you probably won't need to deal with that stuff. Also: reading other scripts is the best way to get a feel for WTF is happening.
Within your scripts, you have the option to define certain event callbacks, which are special functions that get called by the engine or by extra LUA code whenever certain events happen. Event callbacks are distinguished by their name; for example, any function in your code named "eventPacket" will be called whenever a packet is received. Event callbacks are passed parameters that vary depending on the event callback.
Just a little note for you power users: It is also possible to broadcast your own events using the broadcast function. For example, you could write a package that monitors packets received and then, judging from the packet data, broadcasts an "onManaChanged" event. Handling "onManaChange" may be easier for some users than monitoring a packet and dealing with the packet table!
The eventPacket callback is called every time a packet is received. It is passed a single table which contains information about the packet. For more information about the information contained in this table, see Sending and Receiving Packets. Note that this function will only be called if the specified packet is defined by a packet definition.
onManaChange(mana)
This event is broadcast when the player's mana is changed. mana is the new mana of the player. You can access the amount of mana the player has at any time by querying the char.mana variable.
This event is broadcast when the player's health is changed. health is the new health of the player. You can access the health of the player at any time by querying the char.health variable.
Sending and receiving packets is a relatively simple process in scripter. Packets are deserialized according to a packet definition. For example, a packet definition could say that the chat packet consisted of one 2-byte value, followed by a string whose length is determined by that 2-byte value. The packet definition would assign names to those values it specified. Once a packet was received, it could then be deserialized into an LUA table whose fields were populated by the values specified in the packet definition. If you don't know what an LUA table is, don't worry -- just know that you can access these "fields" by doing tablename.fieldname -- this syntax closely matches C structure notation, but instead of calling them "members," they're called "fields." I hope this doesn't sound to complex or dry or anything :D. Anyway, these tables are what you use to send and receive packets in scripter.
Certain fields are universal to all packets. These fields are class and sender.
Packets are sent using the function sendPacket, and they are received by using an eventPacket packet callback.
Besides these two universal fields, packet tables may have other fields as well that vary depending on their class. The basic chat packet has a field chat that indicates the chat text. In the example below, we implement a very basic version of chattoconsole that echos chat to the console using trace.
function eventPacket(packet) if(packet.class == CHAT_PACKET) then -- Is it a chat packet? trace(packet.chat); -- Echo the text of the chat to the console end end
Here's another example where we iterate through the packet table, relaying all keys and values to the console. Tables are actually associative arrays. They store "maps" of LUA data to other LUA data. For example, a table { bleh = "value" } maps the string bleh to the string value. In this table, "bleh" is called the key, and "value" is called the value. I'm probably not explaining this very well. Look on the LUA documentation for more information. Anyway, the example!
function eventPacket(packet)
trace("========== PACKET ==========");
for k, v in pairs(packet) do -- Iterate through the keys (k) and values (v) of the table!
trace(k .. " = " .. v); -- Note that ".." is used to concatenate strings
end
end
As I stated before, "packet definitions" control the de- and re- serialization of binary packet data. Packet definitions provide an extensible mechanism for handling binary data, a task that LUA cannot do very well by itself. Packet definitions aren't particularly daunting. They consist of an XML file that defines a bunch of dators, elements of data. Like a 2-byte unsigned integer would be a dator. Dators have attributes, some of which are universal and some of which are specific depending on the dator. A packet definition consists of many dators which are declared in the order in which they should be read from the binary packet data. Note that packet definition files can consist of multiple packet definitions. EVERYTHING is case sensitive. Everything in the "packets/" folder with an XML definition is loaded at program startup as a packet definition.
So packet definitions begin with the Packet node: <Packet>. The packet node has a couple of attributes:
Contained within the packet node are a bunch of dators. Dators are defined like this: <DatorName name="(name)" attrib="(value)">. Dators have some universal attributes, that all dators can have, such as name. If an attribute isn't specified, then it reverts to a default value. These are listed below.
Okay, and here are the default dators that you can use. Look in the packet definitions for examples.
This is pretty straightforward. Just a string.
Additional attributes:These represent unsigned integral values. They have no special attributes. Each of these dators has a different size. The sizes are listed below. All values are assumed to be in network byte order -- that is, big endian. They are converted to little endian automagically.
A Null dator is written as a series of zeroes (length specified by size) to the packet buffer. It reads size bytes from the packet buffer, but ignores the values of the bytes. You can use Null to indicate a null terminator for a string, or you can use it to represent a padder.
Additional attributes:A container just contains a bunch of subdators. It has no special attributes. In the XML file, dators that are children of a container are nested within that container's XML node. Containers are put into the packet table as subtables. They are read and written linearly from the packet buffer. I can't think of a time when you would need to use Containers, but they were already in the code as a class (the same class that represents packet definitions as a whole), so it took no effort to add them in.
Here is an example packet definition for the chat packet.
<Packet ordinal="e" sender="client" const="CHAT_PACKET"> <Short name="chat_len" hidden="true" /> <String name="chat" size="chat_len" /> </Packet>
Sometimes you may wish to provide services to other LUA scripts, like, for example, a function that allows the user to drop an item by the item's name. In normal scripts, this is not possible because scripts' functions are hidden from each other. This is how each script can have an eventPacket function within them. Packages are different in that every function and variable within a package can be accessed through a table whose name is the package script's filename, minus .lua. For example, if you have a package named "test.lua" with a function "doSomething()," other scripts can access that function by calling "test.doSomething()." You may have noticed that each command related to the inventory is preceded by invo. -- this is because "invo" is a package.
Packages are located in the packages/ folder. On plugin startup, every script in packages/ is loaded as a package.
Sometimes you may wish to have "private" data within a package. You can make functions and variables inaccessible to other scripts by declaring them local. It is important, however, that you do not make event callbacks local.
scripter provides a variety of functions for you to use. Some of these functions are implemented within the plugin itself, the "Core Functions," while other functions are implemented within a variety of packages. Four packages come with scripter by default: invo, spell, comm, and char (char doesn't export any functions).
This function outputs a string to the console. You can "concatenate" values using .. -- for example, trace("The value of myVar is: " .. myVar);.
Loads a script. Paths are always relative to (the directory of laserjesus)\plugins\scripter\. No absolute paths.
Drops a packet. Directly corrosponds to the drop_packet call. You provide it the sender, either CLIENT or SERVER. Note that you can get the sender of a particular packet by doing packet.sender. This example drops every chat packet sent, making the player mute :D.
function eventPacket(packet) if(packet.class = CHAT_PACKET) then dropPacket(packet.sender); end end
This function sends the packet packet, with packet being a packet table. See Sending and Receiving Packets. Here's an example that sends a chat packet, using an inline table declaration ({ ... }):
sendPacket( {
class = CHAT_PACKET,
chat = "Hello, world!"
} );
This function broadcasts an event -- that is, it calls the callback function specified by name in every open script. You can pass it any number of arguments, which will in turn be passed to the callback functions. Here's an example
function myEventCallback(arg)
trace("myEventCallback with \"" .. arg .. "\" was called!");
end
broadcast("myEventCallback", "What's up, bitches?");
This function registers a system-wide hotkey. That is, when you press the specified hotkey, then the callback function func will be called by scripter. The parameters are detailed below:
function hotkeyCallback()
trace("My hotkey was pressed!");
end
hotkey("control", "f", hotkeyCallback); -- Registers the hotkey as CTRL+F
This function will automatically call the function func after delay milliseconds. This function is non-blocking -- that is, it will not wait for delay milliseconds. Note that timers only run once. If you want to have a function that is called every delay milliseconds, then you will have to renew the timer each time the timer callback is called. An example of a timer that renews itself can be found below. Oh yeah, and about param... this can be anything. The param value will be passed to the callback function. If you need to pass more than one parameter to your timer callback, use a table as the param. You can omit param if you want, and you probably will most of the time.
Example:
function timerCallback() -- Called every 1000 milliseconds
trace("This annoying message will come up every 1000 milliseconds -- aren't you glad?!");
timer(1000, timerCallback); -- Renew the timer by creating it again
end
timer(1000, timerCallback); -- Initial creation of the timer
Example showing the use of a param:
function timerCallback(param)
trace("My param is " .. param .. "!");
end
timer(1000, timerCallback, "CAPS LOCK IS CRUISE CONTROL FOR KOOL");
This function sends a string of keys to the Nexus client. It directly corrosponds to send_keys in the LJ API. Example: sendKeys("u");
As of version 1.03, you can, instead of a string, specify a Virtual Key Code instead. Note that the key codes listed on that page are in hex, while you will need to pass the key code to sendKeys as a decimal number. You can convert hex into decimal using the windows calculator. For example, to press enter: sendKeys(13); -- 13 is the decimal virtual key code of enter.
This function makes the player pickup whatever he's standing on. Its the equivalent of pressing , in game.
This function makes the player use an item in his inventory. If you provide this function with a number, then that number is assumed to be the 1-based slot. For example, passing it a 1 makes the player use the item at slot "A:". You may alternatively pass this item a string that represents the name, or part of the name of the item. This name is not case sensitive. For example, you could do invo.use("herb"); to use a herb pipe.
This function makes the player drop an item in his inventory. value can be either the 1-based slot number of the item, or a string or substring (just like invo.use).
This function makes the player say text.
This function makes the player whisper text to the player target.
This function casts a spell. The value parameter can either be a number, indicated the 1-based "slot" of the spell, or it can be the name or part of the name of the spell. Example: spell.cast("soothe");
Variable Reference
Alongside the functions, some packages make various useful variables available to you. These variables are listed below.
This variable is a table. Its a map from inventory slots to subtables. These subtables contain two fields: name and quantity, which should be self explanatory. For example, if you wanted to get the name of the item in the "A:" (1) slot, then you could do invo.registry[1].name.
This variable is a table. Its a map from spell slots to subtables. These subtables contain two fields: name and question. question is the question that is asked for spells that have questions. For example, for gateway, question would be "To which gate do you want to gate or whatever?"
This variable represents the player's health.
This variable represents the player's mana.
Reference for various packets. The LASERJESUS wiki page is provided with each packet exlpanation. The explanations are sparse, and they assume you can get most of your information from the wiki page. I guess this is a bit incomplete, but I'm sick of writing documentation and I want to release this beast tonight! The packets in those incomplete sections can all be manipulated using the functions from the various packages. If you're curious, you can look at the packet definitions themselves in packets/, and then look them up on the wiki and compare notes.