Modding:Tutorial/The Infinite Arena
From DoomRL Wiki
(NOTE: if you are unfamiliar with the general structure of the Hell's Arena module, it is suggested that you read the tutorial before proceeding, as many of the topics found there also are here, and will only be touched upon in this tutorial.)
Hell's Arena is a fun little level, as it can be fairly difficult to complete without some grasp on DoomRL's mechanics, but pillar and monster placement aside, it is still a fairly static map. The following tutorial is a generalized version of Hell's Arena, coined the Infinite Arena, which has been reconstructed based on yaflhdztioxo's design from v0.9.9.1. A fair number of seemingly-superfluous tools have also been created as a means to provide easy customization possibilities for those who want to quickly change the settings of the Arena. If nothing else, you will discover a variety of helper tools in this tutorial that may prove to be useful in your own modules.
Contents |
Special Concept: Custom Tables and Functions
The basics of tables have already been explained in the Game Objects tutorial: it was also explained there that building your own tables is generally unnecessary. There are, however, plenty of reasons to make tables for your own purposes:
- You want the game to randomly choose from a group of variables
- You want to perform calculations or subroutines on many objects simultaneously
- You are creating an object map that other functions may require (e.g., translation table for .run() )
- You want to store a number of variables in an organized manner in order to access them easily
There are two kinds of tables. The first kind are what are more commonly referred to as tables, as they contain keys and values in each of their fields:
local some_table = { key1 = "value1", key2 = "value2", .... }
In fact, these tables can contain more tables, like so:
local big_table = { small_table1 = { key1 = "value1", key2 = "value2", }, small_table2 = { key1 = "value3", key3 = "value4", }, }
Note that these sub-tables do not have to carry the same information, although you may want them to (depending on the table's purpose). In doing so, you can create very large and intricate tables to suit your needs.
The second kind of table is commonly known in the modding community as an array, and is essentially a table that does not explicitly write keys in each field. The following examples show an array, followed by a table that imitates the functionality of an array:
local some_array = {"value1","value2","value3"} local some_imit_table = { [1] = "value1", [2] = "value2", [3] = "value3", } local big_array = { {"value1","value2","value3"}, {"value4","value5","value6"}, } local big_imit_table = { [1] = { [1] = "value1", [2] = "value2", [3] = "value3", }, [2] = { [1] = "value4", [2] = "value5", [3] = "value6", }, }
As you can see, the array format is a lot more compact, but it also forces very specific keys on each field (namely numerical ones). Naturally, tables and arrays can be combined seamlessly, such that you can have a table of arrays or an array or tables (as well as more complicated setups).
When you want to call various parts within a table or array, you can use the following syntax (we will base these on big_array and big_table):
big_table.small_table1 --returns the contents of small_table1 big_table[small_table1] --identical to above big_table.small_table1.key1 --returns "value1" big_table.small_table1[key1] --identical to above big_table[small_table1].key1 --identical to above big_table[small_table1[key1]] --identical to above big_array.1 --returns the contents of the first array in big_array big_array[1] --identical to above big_array.2.2 --returns "value5" big_array.2[2] --identical to above big_array[2].2 --identical to above big_array[2[2]] --identical to above
As a matter of convention, we will write table-like extensions using the dot syntax and array-like extensions using the bracket syntax. (This is typically how other languages write their arrays and tables, and it is often a good idea to organize your code in such a manner so that it is easier to read when looking back at it.)
Functions, like tables, are mostly known based on using parts of pre-defined object classes: in the case of functions, we should already be fairly familiar with their capabilities when calling engine hooks and API methods. The creation of a function is very simple, nearly identical to engine hooks.
local function add_diff(arg1,arg2) val1 = arg1+arg2 val2 = arg1-arg2 return(val1,val2) end local add_diff = function(arg1,arg2) --this line is identical to the first line of the above function .... end
- First you must give the function a name: the above example is called "add_diff".
- Next you must supply input arguments, if any. These are variables that can be used within the function, which is otherwise self-contained. That is to say, a function can't use any variables from anywhere else in your lua script unless they are given through the input arguments. The above example takes in two arguments, generically named "arg1" and "arg2".
- From here we add the script that is to be run when the function is called (this is usually what you concern yourself with when working with the engine hooks). In the above example, the sum of "arg1" and "arg2" is assigned to "val1", and their difference is assigned to "val2".
- Finally, we consider what the function should output. This can be accomplished in two ways when modding:
- You can simply provide outputs to the function using the return() core method. This immediately stops the function and sends outputs to the line that called the function. In the above example, "val1" and "val2" are returned. (Note that, when called, the script should contain some variables so that the return values are properly assigned.)
- You can manipulate objects. Since changing objects (such as their prototype) modify things on the engine side of the game, this can be done without having any particular outputs to the function. Even though the function is self-contained within your script, it can influence things not belonging to the script.
If you want to call a function, use its name with the appropriate input and outputs. For the above example, you could call the function in the following way:
local num1 = 5 local num2 = 4 local sum,diff = add_diff(num1,num2)
The "sum" variable will have a value of 9, and the "diff" variable will have a value of 1.
In the Infinite Arena module, a large portion of the code is dedicated to constructing easy-to-read and well-organized tables, as well as functions that make use of them. The tables and functions will eventually be called in the engine hooks, which leaves us with a relatively small amount of run-time code to sift through. Ideally, modules should have a format in which much of the script is written not as a part of the core module, but as something that the core module will refer to.