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.
If you need help understanding something, please ask your question in the forums (Big thanks to VIPERZOO for hosting!). You can also reach me on AIM or YIM as "misterPhyrePhox" -- just remember that if you ask your question in the forums, everyone can benefit from your questions. There's no such thing as a stupid question, but please do read this manual before you ask your question -- who knows, it could be answered in here! Also: if you're having trouble with LUA the language, remember that there's a large LUA community that'd be willing to help you. There's an IRC channel on freenode #lua. You might also want to check out the tutorials on the lua wiki.
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 broadcastEvent 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 player.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 player.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 monitored and received through the eventPacket 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 value that indicates the chat text. In the example below, we implement a very basic version of chattoconsole that echos chat to the console using print.
function eventPacket(packet) if(packet.class == CHAT_PACKET) then -- Is it a chat packet? print(packet.text); -- 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)
print("========== PACKET ==========");
for k, v in pairs(packet) do -- Iterate through the keys (k) and values (v) of the table!
print(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. Certain special dators can have children, like the Container and Array dators, but we'll get to those later. If an attribute isn't specified, then it reverts to a default value. The universal attributes 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. It represents a string. In Nexus, strings are usually prefixed with their length -- either a 1-byte or a 2-byte unsigned number.
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 represent a padder for data you haven't decyphered.
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.
Arrays are a series of dators that are repeated within a packet. If you're dealing with advanced packets that require the use of an Array, you're probably a programmer, and you know what an array is. Arrays are put into the packet table as an LUA array. Please remember that, in LUA, array indexes start at 1, rather than 0. The additional attributes are as follows:
Here is an example packet definition for the chat packet.
<Packet ordinal="e" sender="client" const="CHAT_PACKET"> <Short name="text_len" hidden="true" /> <String name="text" size="text_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.
This function outputs a string to the console. You can "concatenate" values using .. -- for example, print("The value of myVar is: " .. myVar);. You can also pass tables and other stuff to this function, and it will output them accordingly.
In versions earlier than 1.05, this function was called trace. trace is still a valid function, which does the same thing as print -- this is for backwards compatability.
Loads a script. Paths are always relative to (the directory of laserjesus)\plugins\scripter\. No absolute paths.
Unloads a script. The filename specified must be the same filename you entered for the corresponding loadScript call. You don't have to unload scripts before you load them again; if a script is loaded over itself, the old script will be automatically unloaded.
Drops a packet. Directly corrosponds to the drop_packet call. This function must be called within an eventPacket function! This example drops all chat packets, making the player a mute:
function eventPacket(packet) if(packet.class == CHAT_PACKET) then dropPacket(); 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,
text = "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)
print("myEventCallback with \"" .. arg .. "\" was called!");
end
broadcastEvent("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 keys parameter must be a string like "control shift a" -- then, when control, shift, and the "A" key are pressed at the same time, then the function specified by "func" will be called. You can use special keys like "left" within your keys string -- for example, "control shift left."
Example:
function hotkeyCallback()
print("My hotkey was pressed!");
end
hotkey("control f", hotkeyCallback);
-- Try pressing CTRL+F after running this script!
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. Any arguments after the first two will be interpreted as parameters to the callback function -- that is, they will be passed to the callback function when it is called.
Example:
function timerCallback() -- Called every 1000 milliseconds
print("This annoying message will come up every 1000 milliseconds -- aren't you glad?!");
createTimer(1000, timerCallback); -- Renew the timer by creating it again
end
createTimer(1000, timerCallback); -- Initial creation of the timer
Example showing the use of extra parameters:
function timerCallback(param)
print("My param is " .. param .. "!");
end
timer(1000, timerCallback, "CAPS LOCK IS CRUISE CONTROL FOR KOOL");
This function emulates a keypress on the nexus client. key must be a string like "a" or "left" or "right." This function will work even if the nexus client is minimized. For a list of the keys you can specify, look in system/keymap.txt.
This function makes the player pickup whatever he's standing on. Its the equivalent of pressing , in game.
If you press CTRL+, in game, you will pick up all the items on the ground around you, as long as those items were dropped by monsters you killed. You can try this some time. This function will send the packet that is the equivalent of pressing CTRL+, 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 drop amount money.
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. The param argument is a spell parameter; for example, spell.cast("gateway", "n"), will make you gate to the north gate. The param argument can be omitted. Example: spell.cast("soothe");
This function toggles "developer mode" -- when developer mode is enabled, when you right click on an object, information about that object's coordinates and unique ID will be displayed in the status window.
Given a monster type or "players," go.status will output information about those monsters or players on the screen. If you omit kind, go.status will output information about all the objects on the screen
This function will return information about an object at the specified coordinates. If there is not an object at those coordinates, this function will return nil.
If a player with the name name is on the screen, this function will return the uid of that player; otherwise, it will return nil. This function matches substrings and is case-insensitive.
Returns an array containing the uids of objects who are named name. This function matches substrings and is case-insensitive.
Example:
-- Print the coordinates of all trees in the area
for k, v in pairs(go.selectObjects("tree")) do
local x = go.registry[v].x;
local y = go.registry[v].y;
print("There is a tree at (" .. x .. ", " .. y .. ")!");
end
Returns an array containing the uids of adjacent objects that are named name. The parameter name is optional. This function matches substrings and is case-insensitive. An object is "adjacent" if it is on one of the four squares around the player, north, east, south or west; no diagonals!
This function will run an automation script. Only one script can be run at a time. See the section on the automation system. Please know that script execution will STILL GO ON even after this function has been called; the currently executing LUA script will not pause while the automation script is run.
This function will stop the currently running automation script.
The first parameter, t is an array of tables. This function will search through t looking for table entries which have key-value pairs which match up with the key-value pairs in the table, props. It then returns an array containing the keys of the entires who match. I can't explain it very well, but I think this function will be very useful for dealing with the various registries. I'll illustrate what this function does with a couple examples:
Example:
-- Select all go.registry entries whose name is "Squirrel"
local squirrels = tablex.select(go.registry, { name = "Squirrel" } );
-- The variable squirrels is now an array containing the unique ids of all the squirrels
-- Select all invo.registry entries whose name is "Axe"
local axes = tablex.select(invo.registry, { name = "Axe" } );
-- The variable axes is now an array containing the slots of all axes in the players inventory
-- Get a list containing the uids of all players
local players = tablex.select(go.registry, { kind = go.PLAYER } );
-- Get a list containing the uids of all monsters
local monsters = tablex.select(go.registry, { kind = go.MONSTER } );
-- Et cetera!
This function returns true if the table t contains the value v.
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?"
The uid of the player.
These two variables represent the player's coordinates. If there is lag, these variables may be temporarily rendered inaccurate, until the player gates.
This variable represents the player's health.
This variable represents the player's mana.
This variable contains a list of all the game objects on the screen. See the section on the go package.
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.
The go package was added in version 1.05. This package allows you to track all "game objects" -- players, monsters, and dropped items --by name. The go package doesn't contain very many functions -- but it keeps the variable go.registry updated. The "registry" contains a list of all the game objects on the screen, along with their coordinates and other useful information. The registry is an LUA table that "maps" object unique IDs to information about that monster. In case you didn't know, every monster in nexus is assigned a unique ID. Yeah. Anyway, the fields of a registry entry are as follows:
Related functions: go.findObject, go.devMode, go.status.
Here is an example that will output the name of the object that is to the left of the player:
local object = go.findObject(player.x + 1, player.y);
if(object == nil) then
print("No object is to the left of the player!");
else
print("The object " .. object.name .. " is to the left of the player!");
end
This go package isn't flawless yet, and sometimes names can be a little off, but hopefully it should work well enough for most things.
(If you're the impatient type, skip to the end of this section and read the example!)
The auto package contains functions dealing with the automation system. The automation system allows you to create mini "scripts," which consist of an array of commands. When these scripts are "run," the commands within the script are executed in order. Examples of commands include: Moving horizontally, moving vertically, saying something, casting a spell, using an item, or waiting for a specified time. By default, commands are executed at an interval of 500 ms. The most advanced feature of the automation system is the ability to move horizontally or vertically to a specified coordinate. When the automation system is moving the player, the automation system will automatically detect when it is blocked. It will then us the go package to determine what is blocking it, and depending on whether it is set to be hostile, it will either walk around the obstruction, or attack the object that is blocking it until the object is dead. So it can walk around players and attack monsters that are blocking it, or whatever.
As I said, before, scripts are arrays that contain commands. Commands, in turn, are tables. A required key in any command table is the cmd field, which indicates the type of command. The other fields in the command table vary depending on the command. The different commands are listed below, along with any special fields they have.
Additionally, there are some non-commands fields you need to put in your script table thing:
You can execute scripts using auto.run, and stop script execution using auto.stop.
I think there are some pretty obvious possible uses for the automation system. It was designed so that users could craft scripts that would automatically make the player go to the inn, deposit ambers or whatever (harvested using a hunting bot), and then walk back to the hunting area. Maybe you can find some more uses for it.
REMEMBER: The function auto.run DOES NOT stop your LUA script from executing -- that is, the auto.run function WILL NOT PAUSE while the script you passed it executes. If you want to, for example, re-enable a hunting bot once a script is executed, you will need to put a RUN_FUNC command at the end of your script that will call a function to re-enable the hunting bot.
ANOTHER IMPORTANT NOTE: You know when you walk into doorways and stuff? If you wish to automate walking into or out of a doorway, you MUST create a auto.VERT_MOVE or auto.HORIZ_MOVE command that will move the player to RIGHT BY the doorway. You must then create an auto.RUN_FUNC command that will execute a sendKey("left") command (you can pass the parameter "left" to the sendKey function by setting param = { "left" } in the auto.RUN_FUNC command), or whatever direction the doorway is in, in order to make the character move that final step into the doorway and into the other area.
A FINAL IMPORTANT NOTE: When you are changing servers, there is a loading screen; the automation system does not automatically detect that there is a loading screen -- you must pause your script accordingly, by inserting an auto.WAIT command which will wait long enough such that the client is loaded.
Okay, yay, those are all the important notes. Sorry about these limitations, I'm not sure if theres a way to get around them.
AND FINALLY! The example! This example was made for the Buya inns; it will use a yellow scroll to get you to the buya inns, then walk over to the lady person and deposit all your amber. It will then exit the inn and go to north gate.
funtion errorHandler() -- This function will be called if the automation system encounters an error
invo.use("yellow scroll"); -- Use a yellow scroll to get us back into the inn
end
my_script = {
hostility = auto.MONSTERS_HOSTILE, -- If a monster blocks our path, then attack it!
on_error = errorHandler, -- Set our error handler
{ cmd = auto.USE_ITEM, value = "yellow scroll" }, -- Use a yellow scroll to warp us to the inn
{ cmd = auto.VERT_MOVE, kind = auto.ABSOLUTE, value = 7 }, -- Move up to the middle of the inn
{ cmd = auto.HORIZ_MOVE, kind = auto.ABSOLUTE, value = 16 }, -- Move right towards the NPCs in the right section
{ cmd = auto.SAY_TEXT, value = "I will deposit all Amber" }, -- Deposit all your Amber through a say command
{ cmd = auto.HORIZ_MOVE, kind = auto.ABSOLUTE, value = 4 }, -- Move back towards the door on the left
{ cmd = auto.VERT_MOVE, kind = auto.ABSOLUTE, value = 12 }, -- Move down towards the door
{ cmd = auto.RUN_FUNC, value = sendKey, param = { "down" } }, -- Send the "down" key to move you through the door
{ cmd = auto.CAST_SPELL, value = "gateway", param = "n" } -- Goto north gate
}
auto.run(my_script); -- And finally, run the script!
This section is designed for beginners. If you are an intermediate or advanced programmer, most, if not all of this will be review. You might learn a couple of interesting things, but whatever. Anyway, here we go...
Throughout this document, you've heard me refer to these things called "tables." In this little extra section, I'm going to explain the many roles of the multifaceted LUA table, and how you can use these tables. Tables are a very powerful thing in LUA, and they're not that complicated -- but if you don't know what tables are and how to use them, you're never going to be able to use scripter and LUA as they were meant to be used.
I'll start off by introducing you to the concept of "containers." Simply put, containers are objects that hold other objects. In traditional programming languages, you might have many different constructs to represent different types of containers. In LUA, all the container types can be represented through the use of a table.
Lists are just what you think they would be; they are linear sets of data. For an example, lets represent the foods in my fridge with a list, by enclosing the contents of the list in curly braces { }, and separating the elements of the list with commas: foods = { "apples", "oranges", "pie" }. The question, then, is how do we access and modify this list of foods? Each object in the list is assigned a number called an index. The index of the first item in the list is 1, while the index of the second item in the list is 2, and so on. So in order to access the first item in our list, we use double brackets [] like so: foods[1]. We could just as easily change the contents of the first item by assigning a new value: foods[1] = "mangos".
What if I remove pie from my fridge, because the sight of pie makes me sick. Pie is disgusting. I hate it. But -- how do we update the contents of our list to reflect this change? We can use the function table.remove, which accepts a table and an index: table.remove(foods, 3), where 3 is the index of "pie."
So now lets tackle a third operation: How does one add items to the list? But before we add items to the list, we must know where we want to add these items. Do we want to add them at the front, making our new item the first item in the list and shifting every other item forward, or do we want to add our item at the back of the list? Either way, adding items to an lua list is made simple through the use of the table.insert function. If you give table.insert two arguments, a list and an item, then table.insert will add that item to the end of the list: table.insert(foods, "rice krispies"). But sometimes that is not sufficient; sometimes we must add an item to the beginning of the list, or at an arbitrary position in the list. We can do this by passing table.insert a third argument, in the middle, which represents a position to insert the item at: table.insert(foods, 1, "yummy stuff") would insert the string "yummy stuff" at the beginning of the table, making foods[1] == "yummy stuff". table.insert shifts the other elements of the list around to make room for "yummy stuff" -- that is, "apples," which was at index 1 before the insertion, would now be at index 2.
At first glance, the name "associative array" may seem daunting, but its not that complicated. Associative arrays are used to associate certain objects with other objects. For example, the statement "pie is bad, but lua is good" could be represented by an associative array that associates "pie" to "bad," and "lua" to "good." In the associative array, "pie" and "lua" are referred to as keys, while "bad" and "good" are called values. Association is one-way, so we can use the key "pie" to "unlock" the data that is associated with it -- "bad." We cannot do the reverse -- that is, ascertain "pie" using the value "bad." Associative arrays are also referred to as maps, and "to map" something is the same thing as "to associate" something; I use the terms interchangeably.
In LUA, the syntax for creating a table is very similar to the syntax used to create a list; we still use curly braces { } to enclose the associative data. Instead of listing objects, though, we list key-value pairs, assigning keys to values using equals =: map = { "pie" = "bad", "lua" = "good" }.
In order to access the data contained within the map we just created, we can use brackets [] -- instead of putting numbers within the brackets, however, we put the key whose value we want; map["pie"] == "bad". We can also assign values just as easily: map["pie"] = "somewhat jolly". We can also assign arbitrary keys, which we have not referenced before now: map["oranges"] = "they are orange". Removing key-value pairs is also trivial: just set the key equal to nil, which means "nothing" -- map["oranges"] = nil would remove the key-value pair "oranges" = "they are orange" from the associative array. Likewise, if you try to access a key that has not yet been assigned, the value will be nil: map["WAAAGH!"] == nil.
In most programming languages, you have a container type called structures, which contain various fields (also called members). These members are then assigned values. For example, you could create a structure to hold information about a point, with two members, x and y. If you think about it, this whole member-value concept closely resembles the key-value concept that we discussed earlier. And, in fact, the way that you assign members is very similar to the way that you assign key-value pairs, except that member names are not enclosed in quotes, even though they could be thought of as strings: point = { x = 10, y = 20 }.
In order to access the members of a structure, we depart slightly from the bracket [] notation that we used before. Instead, we use periods. For example, to access the x member of our point: point.x == 10. Assigning values is just as easy: point.y = 30.
By now, I've introduced you to three different container concepts, and I've shown you how they can be represented using an LUA table. You might be wondering how these three things can be represented using only one data structure, and the answer is that everything is an associative array. Lists are associative arrays that associate indexes to objects; that is, keys are consecutive numbers, and values are arbitrary objects: list = { "pie" } is the exact same thing as list = { 1 = "pie" }. Structures are associative arrays that map member names (which are, in fact, strings) to their values: struct = { member = "value" } is the exact same thing as struct = { "member" = "value" }, and struct.member is the exact same thing as struct["member"]. Nifty, aint it?
Iterating is just the process of going through ever key-value pair in a table. We can iterate through tables in lua using the for statement:
for k, v in pairs(table) do
print("The key is " .. k .. " and the value is " .. v);
end
EOF, LOL!