The dark and chilling silence was broken by the blood curdling scream of a modem. One witness later identified it as 28.8 kbaud from the final strangulated burst of static.
A list is a data object which contains a number of values. The items in the list are numbered 1, 2, 3, to the last in the list. This number is called the index of the item and is used to access it. This makes the list datum in DM similar to an array in the C language.
There are several ways to declare a list variable. One is to use the
list object type as you would with any other type of variable. The
others use a special syntax designed specifically for lists.
The first and second methods are identical. They define a list variable.
Note however, that they do not actually create a list object. It is just
the same as when you define a variable of any other type (like a mob). Only
the type of the variable has been defined. The contents of the variable are
still initialized to null.
The third and fourth methods for defining a list create a new list object in
addition to defining the variable. The specified size indicates how many
items to allocate room for in the list. Since the size can be changed
dynamically (that is whenever you need to), an initial size of 0 may even be
specified.
An individual item in the list may be accessed by putting the item's index
inside braces [ ]. This is called indexing the item.
The following (useless) example creates some objects and stores them in a
list.
The individual dwarves can then be accessed at any time by using their
indices:
It is an error to specify an index that is beyond the end of the list.
Doing so will cause the procedure to crash. That means you should be
careful to stay within the bounds of the list, which brings us to the next
topic.
The length of a list is stored in its len variable. This can be
changed to resize the list. More commonly it is used to find out how many
items are in the list.
The previous example could be improved so that the number of dwarves is only
specified in one place.
This is a very common use of the for loop. Note the difference
between this where we are looping over the indices of a list and the other
type of for loop, 1. Declaring a List
2. Accessing List Items
var/dwarves[7]
world/New()
var/i
for(i=1,i<=7,i++)
dwarves[i] = new/mob/dwarf()
dwarves[1]
, dwarves[2]
to get the first dwarf,
second dwarf, and so on.
3. len variable
var/dwarves[7]
world/New()
var/i
for(i=1,i<=dwarves.len,i++)
dwarves[i] = new/mob/dwarf()
for(i in dwarves)
, that iterates over the items in
the list. The indices are necessary, as in this example, when you need to
assign the item to a different value.
|
This leads to yet another way of initializing the seven dwarves.
var/dwarves[0] world/New() var/i for(i=1,i<=7,i++) dwarves += new/mob/dwarf()
In that example, individual items are added one at a time. In the next example, the ability to operate on an entire list is exercised to make an annoying little trap:
turf/naked_maker Enter(mob/M) contents += M.contents return 1
Do you see what it does? One thing you must know to understand what happens here is that contents lists are handled specially. When an object enters one contents list, it is automatically removed from any other. So when the user's contents are added to the turf's contents, all the objects in the user's inventory drop to the ground. Beware of the naked maker!
|
|
|
This could be used, for example, to determine which of the seven dwarves a given dwarf is.
mob/dwarf verb/which() set src in view() var/n = dwarves.Find(src) usr << "I am the [n]\th dwarf. Where is Snow White?"
This way, you can happily wander around asking each dwarf which one they are
and they will tell you. Notice the use of the \th
text
macro. That produces the correct ordinal ending: 1st, 2nd, 3rd, and so on.
|
Using this, you could check if a given dwarf is one of the seven with the
statement if(D in dwarves) ...
.
A more complex example using this would be a list of super-users.
var/SuperUsers[0] client/New() if(key in SuperUsers) usr = new/mob/DM() usr.name = key usr.key = key //connect the player return ..()
Anyone in the special SuperUser
list gets a mob of type /mob/DM
.
Everyone else gets the default world.mob. Of course, you would need
to add some keys to the SuperUser
list for this to have any effect.
The Copy proc creates a new list from the contents of all or part
of the source list.
4.5 Copy proc
|
The Cut proc removes a section (or all) of a list.
4.6 Cut proc
|
There are several instructions in DM for creating lists. Each one
initializes the contents of the list in a different way. These are
described in the following sections.
The list instruction creates a list from its arguments.
5. Creating Lists
5.1 list instruction
|
The most common place where one uses this is in the argument definition of a verb.
mob/verb/set_gender(G in list("male","female","neuter")) gender = G
This is a more user-friendly version of the gender changing verb. Instead of having the user type into an open-ended text field, this verb restricts the choice of gender to three allowed values.
|
This instruction can be used to initialize the contents of an object. In the following example, new players will be created with two objects already in their inventory.
mob/player contents = newlist(/obj/scroll/introduction, /obj/food/fortune_cookie)
Notice that DM permits arguments to be placed on successive lines. That applies to any procedure--not just newlist. Any amount of space, newline, and indentation may follow each comma. When argument lists get very long (as they may with list and newlist), it is sometimes nice to format them in a column rather than a single line.
|
This instruction often saves one from having to code the same list manually (and then maybe forgetting to update it when adding a new object type). As an example, suppose you wanted the super-user to be able to create any object at will.
mob/DM verb/create(TypeOfObject in typesof(/obj,/mob)) new TypeOfObject(usr) //Presto!
This has the user enter a raw type name (like /obj
or
/obj/scroll
). That's ok for the DM, but one could make it more
friendly by having the user select from a list of objects rather than types.
var/objects[0] world/New() var/T for(T in typesof(/obj,/mob)) objects += new T mob/DM verb/create(O in objects) new O:type(usr)
The global list of objects is initialized in world/New(). Of course to be used in this way, we are assuming that all the objects have unique names. All sorts of variations are possible. For example, you could have an object variable indicating if certain object types should be excluded from the list or if an alternative name should be used, etc. The typesof instruction is a useful tool when writing generalized code such as this.
So far, you have seen several object variables which are also lists. This
section will review these and add a new list to your bag of tricks.
It is worth noting that the pre-defined lists are each used so frequently
that they are implemented specially by DM for improved efficiency. For the
most part, they behave just like lists that you might create yourself, but
in some cases there are subtle differences. These will be mentioned in the
sections that follow.
All the game objects (and the world object too) have a contents
variable. In the case of the world, this contains all mobs, objs, turfs,
and areas. Areas contain turfs, mobs, and objs. In all other cases, the
contents are mobs and objs. No other object types may be added to these
lists. Each game object exists in the world contents list and possibly the
contents of one other object, depending on whether the object's loc
is null or not.
Order of objects in the world contents list is not meaningful. Newer
objects may exist before or after older objects because of the way the list
is managed internally. The order in all other contents lists is from oldest
arrivals to newest, with objs first and mobs following. This is the same
order in which the objects are displayed on the map.
Another peculiarity about contents lists is that every object has its own
fixed list. It is not possible, for example, to make the contents variable
of an object refer to another list. If you try to assign it, the list will
be copied rather than shared. This is different from the normal behavior in
which assignments merely copy references to objects rather than duplicating
the object data.
The following example takes advantage of the order of contents to make the
center key on the keypad pick up the top most object.
This works by looping through the turf contents and saving a reference to
the last one. Another way to accomplish the same thing would be to manually
loop through the contents list in reverse. In either case, the reason for
using a loop rather than just taking the last item in the contents list is
to filter out mobs.
The contents of areas are handled a little differently from other objects.
Normally, only those objects directly inside another are listed in its
contents list. For example, if a player is carrying around a bag object,
the contents of the bag are not listed in the player's contents.
In the case of areas on the map, however, this would result in only turfs
being listed in the contents of the area. As a convenience, the contents of
turfs are also included in areas.
Using this fact, you could make a
First a reference to the user's area is obtained. We could have just set it
equal to
The
The real objects and the client object all have verb lists. These contain a
list of all the verbs attached to the object. By manipulating the verb
lists, commands can be added and removed at run-time.
A reference to a procedure is obtained by specifying the path to its
definition. It is this value that exists in the verb list. Note that there
is no difference between proc and verb references. You can add either one
to the list of verbs.
The next example allows a player to memorize spells from a book and thus
permanently add a command to their repertoire.
To add another spell to the book, one would create a verb for reading it and
a corresponding proc that will be added to the player's list of verbs.
Another way to add to an object's verb list is by using new. This
method also provides a way to assign a new name or description to the verb.
6. Pre-Defined Lists
6.1 contents list
client/Center()
var/obj/O
var/obj/LastO
for(O in usr.loc) LastO = O
if(LastO) //top most obj
LastO.Move(usr)
6.1.1 Area Contents
sniff
command which informs the user
about other creatures in the area.
mob/wolfman
verb/sniff()
var/A = usr.loc
//loop through usr's containers
//until we hit an area
while(A && !istype(A,/area))
A = A:loc
usr << "You smell:"
var/mob/M
for(M in A)
usr << "\a [M]"
usr.loc:loc
, that is the area containing the user's turf.
However, the method used is more general. It works even if the user is
inside an object other than a turf. If that is not possible in your game,
then you can just assume the player is always located on a turf.
\a
macro used in the output text generates the correct indefinite
article for the following object. For example it would produce "a cat" in
one case and "an elephant" in another. With a proper noun like "Dan,"
it produces nothing at all.
6.2 verb lists
obj/spellbook
verb/read_stunray()
usr.verbs += /obj/spellbook/proc/stunray
proc/stunray(mob/trg)
set src=usr
view() << "[usr] immobilizes [trg] with a mysterious gesture."
|
The read_stunray
verb can be rewritten as follows:
obj/spellbook verb/read_stunray() new/obj/spellbook/proc/stunray(usr)
The args list is a proc variable that contains each of the
parameters passed to the procedure. This can be used to create procedures
that take an arbitrary number of arguments.
An example using this is a procedure like typesof, except it
only includes final types--that is, object types with no children.
By using the args list, we allow for multiple arguments being
specified to
The lists described so far have been single-dimensional, meaning that a
single index is specified to retrieve a value. Multi-dimensional lists may
also be created. The most common case would be a two- or three-dimensional
list used to hold some information about the map.
The following are two ways to declare a multi-dimensional list.
These two examples each create a 2-dimensional list. Such a list is really a
list of lists. In this case,
Whether you use new to create the list or specify the size
directly in the brackets depends on whether you know the size of the
dimensions at the time when the variable is defined.
The elements in a multi-dimensional list are accessed by specifying an index
for each dimension in brackets. If fewer dimensions are specified, then the
result is itself a list containing all the values for the missing dimensions.
The following example creates a copy of the map in a three-dimensional
list. This could be used to restore the map at a later time.
To get a reference to the turf at a given coordinate, the locate
instruction is used. The procedure simply loops over all coordinates on the
map and stores the type of turf found there.
An associative list is one in which unique items in the list are paired with
other values. Associated values are accessed by using the list item
as the index into the list. The item is still accessed by using its
numerical index.
The following code demonstrates how to construct a list such as the one in
figure 10.13.
Notice that the items
Having constructed the list of building materials, you can now make a verb
that puts it to good use.
This provides a nicer looking list of building materials than the raw type
paths. Players see
This example emphasizes the fact that
The same methods for looping through lists work with associative lists. In
fact, whether a list contains associated values or not really has no bearing
on the matter. However, it is instructive to see how the associated value
is accessed in the process.
Displaying the contents of the
That method loops through the items in the list. It can also be done by
looping through the numerical indices.
Notice the difference between
From the preceding discussion, you can gather several things. One is that
numerical list items may not have associated values. The reason is that a
numerical index is treated differently from an object or text string index.
The former is referred to as an array index and the latter is
referred to as an associative index. There is no way to specify an
associative index for a number. (However, you could store the
number in an object, or convert it to a text string.)
Another thing to note is that items in an associative list should generally
be unique. If two list entries refer to the same item, they cannot each
have an independent association. Instead, they share the associated value.
As long as you only add items implicitly by using them as an index in the
assignment of an associated value, there is no need to worry about
uniqueness. New entries will only be formed for items which do not already
exist. Only if you explicitly add items (using list.Add() or
+=) is there any possibility of duplicate items.
Another related question is what happens if you try to get the associated
value for an item which does not exist in the list (like
As a final note on the implementation, be warned that the special lists
(such as contents) do not support associative values. Of course, if
anyone can come up with a good reason why they should, that may change.
One very common use for associative lists is to contain a list of parameters.
By that, I mean a list of unique names and associated values.
The world.params list, mentioned in section 8.1, is an
example. It allows a world to receive arbitrary parameters from the person
who starts it up. While that isn't something you would want to do very
often, it does allow a DM program to adapt to various conditions or to
perform different functions.
The -params command-line option to Dream Daemon is used to
initialize the parameter list. Multiple parameters may be assigned in a
single argument or several -params may be specified.
6.3 args list
proc/final_typesof()
var/BaseType
var/DerivedType
var/FinalList[0]
for(BaseType in args)
for(DerivedType in typesof(BaseType))
var/children[] = typesof(DerivedType)
if(children.len == 1) FinalList += DerivedType
return FinalList
final_typesof()
just like the typesof()
procedure.
7. Multi-Dimensional Lists
7.1 Declaring the List
var/MyList[5][10]
var/MyList[][] = new/list(5,10)
MyList
is a list of five lists.
Those five lists each have a length of ten.
7.2 Using the List
var/initmap[][][]
proc/CopyMap()
var
x; y; z
initmap = new/list(world.maxx,world.maxy,world.maxz)
for(x=1, x<=world.maxx, x++)
for(y=1, y<=world.maxy, y++)
for(z=1, z<=world.maxz, z++)
var/turf/T = locate(x,y,z)
initmap[x][y][z] = T.type
8. Associative Lists
Figure 10.13: An Associative List
Index Item Value
1 "wall"
/turf/wall
2 "floor"
/turf/floor
3 "door"
/turf/door
var/materials[0]
proc/InitMaterials()
materials["wall"] = /turf/wall
materials["floor"] = /turf/floor
materials["door"] = /turf/door
"wall"
, "floor"
, and "door"
are
implicitly added to the list, merely by assigning their associated values.
We could have begun by doing
materials.Add("wall","floor","door")
, but that would have
been redundant.
mob/verb
build(m in materials)
var/mtype = materials[m]
new mtype(usr.loc)
"door"
instead of /turf/door
.
materials
behaves in every way
like a list of text strings, not a list of object types. Only when indexed
by one of the text strings does it reveal the associated value, which in
this case is an object type.
8.1 Looping
materials
list can be done as follows.
mob/verb
list_materials()
var/m
for(m in materials)
usr << "[m] = [materials[m]]"
mob/verb
list_materials()
var/i
for(i=1,i<=materials.len,i++)
var/m = materials[i]
usr << "[m] = [materials[m]]"
materials[i]
and materials[m]
.
The first is a text string and the second is the associated object type.
8.2 Specifics
materials["window"]
). The result will be null, but the index
item is not added to the list. In this way, you can be sure that the
list is never modified when reading from it, only when writing to it.
9. Parameter Lists
9.1 world.params
|
This produces a world.params list with items "name1"
,
"name2"
, and so on, with the corresponding associated values. A
semicolon ; may be used in place of & to separate fields.
There are a few other special characters that will be described in the
following section.
|
As a simple example, the expression
params2list("DM=James+Byond&SaveFile=myworld.sav")
would produce the following parameter list:
Index | Item | Value |
1 | "DM" | "James Byond" |
2 | "SaveFile" | "myworld.sav" |
Notice that + in the parameter text is translated into a space. In
this case, we could have simply used a space instead, but the official format
encodes all characters other than letters, numbers, periods, and dashes
specially. (The standard MIME type
being used here is application/x-www-form-urlencoded
for you CGI
programmers out there.) That allows the parameter text to be used on the
command-line or in other contexts where special characters might be
interpreted strangely.
All special characters other than spaces are encoded using an ASCII value. The format is %xx where xx are two hexadecimal digits representing the ASCII value. A few common cases are listed in figure 10.15.
(Hexadecimal is a base 16 number system. Digits 10 through 15 are written in either upper or lowercase as A through F.)
Character | Hex-code |
= | %3d |
& | %26 |
; | %3b |
+ | %2b |
\n | %0d%0a |
|
You could use list2params to pack several parameters into a topic link. Then when the link is executed, you could use params2list to extract the information.
The following example does that to form an inter-world communication channel.
mob/verb/broadcast(msg as text,address as text) var/plist[0] plist["function"] = "broadcast" plist["speaker"] = usr.key plist["message"] = msg //broadcast message to world at given address world.Export("[address]#[list2params(plist)]") //here is where we receive messages from other worlds world/Topic(T) var plist = params2list(T) function = plist["function"] if(function == "broadcast") var speaker = plist["speaker"] message = plist["message"] world << "[speaker] broadcasts, '[message]'" else return ..()