And the earth was without form, and void; and darkness was upon the face of the deep.
The world map is a three-dimensional grid of turfs. Three coordinates (that is, numbers) are necessary to pinpoint the location of an individual turf in the grid. These have the symbolic names x, y, and z.
The x coordinate specifies east-west positioning. A value of 1 is the western edge of the world and a value of world.maxx is the eastern edge. On the player's screen, x increases from left to right. The y coordinate goes from 1 in the south to world.maxy in the north and normally increases from the bottom to the top of the screen.
The z coordinate goes from 1 to world.maxz and often represents high to low altitude. However, the interpretation of z is entirely up to your code since there are no built-in procedures that move objects between z-levels. Level 1 could be ground level and subsequent levels could descend into the earth. Or level 1 could be a world map and the other levels could be detailed city maps. It's entirely up to you. Most action takes place in the x-y plane because that is what players can see.
|
The default view range may be adjusting by setting world.view. The default is 5, which gives you an 11x11 viewport. However, you can increase it up to a maximum of 10, which gives you a 21x21 viewport. (The size of icons may be automatically scaled in order to conveniently fit the map viewport on the player's screen.)
As a convenience, the arguments may be specified in any order. This feature
is most often used when one wishes to specify a different center of view
while still using the default range. For example, if you wanted src
rather than usr as the center, you could write view(src)
rather than view(5,src)
or even view(,src)
.
The range of -1 includes the center object and its contents. A range of 0 adds the center object's turf or room and any other objects inside it. A range of 1 extends to the region on the map one square away from the center (in a diagonal or straight direction). A range of 2 includes the next line of turfs and so on. The default range of 5 includes the entire 11x11 map seen by the player.
2 | 2 | 2 | 2 | 2 |
2 | 1 | 1 | 1 | 2 |
2 | 1 | 0 | 1 | 2 |
2 | 1 | 1 | 1 | 2 |
2 | 2 | 2 | 2 | 2 |
Portions of the view may be blocked by opaque objects. Lighting also plays
a role. If the background area lighting is on (area.luminosity =
1
), all objects inside are illuminated. Otherwise, individual turfs or
objects on the map may be luminous, lighting up themselves and the objects
around them. If nothing is lit up, only the objects immediately around the
center (up to a distance of 1) are visible.
The way in which opaque objects affect the view is rather complicated. Roughly speaking, opaque objects block the view of anything behind them. To improve the appearance of the view, any opaque objects on the edge of this strictly visible region are also made visible. This is known as boundary highlighting and often yields a much improved effect.
The oview instruction is the same as view except it
excludes the center object and its contents. In other words, it excludes
the objects in 1.2 oview list
view(-1)
. This is most often useful when
broadcasting a message to everyone in view except the perpetrator of some
action.
|
|
Using locate() one could place new players on the map at a specific coordinate. The following example moves them to the middle of the map rather than the default position at (1,1,1).
mob/Login() if(!loc) //new player loc = locate(world.maxx/2,world.maxy/2,1)
This is just one of many cases in which locate() is used to access an object in the code that was created on the map using the map editor. Rather than specify a coordinate, it is usually more convenient to use the tag variable of the object. This can be assigned to a special value and used to find the object at run-time.
The following example moves new players to a specially tagged location.
mob/Login() if(!loc) loc = locate("start")
A turf would have to be tagged "start" for this to work. Editing the tag variable, as well as other properties of objects, in the map editor will be discussed in section 14.3.
|
The following example uses this to find a particular turf in a region of the map.
proc/LocateInLevel(Type,zLevel) var/SW = locate(1,1,zLevel) var/NE = locate(world.maxx,world.maxy,zLevel) return locate(Type) in block(SW,NE)
This procedure could be used to connect z-levels of the map. If each level had one up-stairs and one down-stairs turf, these could be connected in the following manner.
turf/downstairs verb/use() var/dest = LocateInLevel(/turf/upstairs,z+1) usr.Move(dest) turf/upstairs verb/use() var/dest = LocateInLevel(/turf/downstairs,z-1) usr.Move(dest)
There are many other ways of making turfs which transport the user from one location to another. The destination could be in some fixed position relative to the original turf (for example z+1 or z-1). Another useful method is to mark the destination with a special tag.
|
This could be used to determine if the target of an action is within range as in the following example.
mob var/mob/enemy proc/Swing() if(get_dist(src,enemy) > 1) return //do the damage...
In this case, mobs can only strike targets in neighboring positions. The details of the rest of the procedure have been omitted. A typical combat system requires some randomness, which you will see how to do in chapter 16.
There are a number of instructions relating to movement of objects on the
map. They are listed in figure 14.25.
2. Movement
Figure 14.25: Movement Instructions
walk |
walk_towards |
walk_to |
walk_away |
walk_rand |
step |
step_towards |
step_to |
step_away |
step_rand |
get_step |
get_step_towards |
get_step_to |
get_step_away |
get_step_rand |
|
The three main groups are walk, step, and get_step. These each perform the same computation but differ in how they apply the result. The walk instructions continually move an object, taking multiple steps if necessary. The step instructions do the same except only a single step is taken. The get_step instructions do not move any objects, but return the next location that would be stepped to according to the given walking algorithm.
Which group of movement instructions you would want to use depends on how much control you need to take over the process of moving an object. The walk group of instructions completely automates the movement, whereas step allows you to control the timing yourself. For complete control, you can use get_step so that even the decision about whether to move the object or not is left up to you.
The available walking algorithms are described in the following sections. When a ready-made algorithm does not exist to suit your purpose, you may still be able to use one of these in conjunction with your own additions.
|
The direction argument takes one of the constants NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, or SOUTHWEST. These are the same values used to indicate the direction an object is facing with the dir variable.
The step instruction returns 1 on success and 0 on failure and get_step returns the next turf in the given direction. The walk instruction returns immediately and continues to operate in the background since it sleeps before each step.
Only one walking operation may be in effect at one time on a particular
object. That means that when walk is invoked, any previous walking
operation on that object is aborted. To clear any existing walking
operations, one can therefore specify no direction at all:
walk(Obj,0)
.
There are a couple of related instructions for dealing with directions. These are described next.
|
The turn instruction rotates a direction by the specified amount.
2.1.2 turn instruction
|
The angle is specified in degrees. For example, turn(NORTH,90)
yields WEST, a 90 degrees rotation in the counter-clockwise
direction. Negative angles may be specified to achieve clockwise rotations as
well.
The following example defines a guard mob who paces back and forth continuously.
mob/guard/New() ..() spawn for() //spawn an infinite loop if(!step(src,dir)) dir = turn(dir,180) sleep(30) //three seconds
By changing the initial direction the guard is facing, he can be made to pace in the desired line. This example shows how you can use the existing walking algorithms for your own purpose--in this case a linear pacing algorithm. Rotating by 90 degrees or 45 degrees instead would produce motion in two dimensions instead of just one. Of course then the guard might wander off and neglect his duties!
|
The return values of these are the same as the fixed-direction movement instructions that have already been described. In fact, all movement instructions behave the same except for the specific stepping algorithm that is employed.
|
One use for this would be a verb that allows a player to automatically follow another one. That can save a lot of needless key presses, which may otherwise bog down the network. Note that the command takes the player's current distance from the target as the desired range to maintain so that one can avoid crowding in on the leader.
mob/verb/follow(mob/M) walk_to(src,M,get_dist(src,M),30)
As a convenience in situations like this, pressing any direction key will
stop the automated walking algorithm. This applies even to the center key,
which merely calls walk(src,0)
by default, allowing the player to
stop in place.
|
An example using this algorithm is a command to run away from someone.
mob/verb/flee(mob/M) walk_away(src,M,5,30)
|
The following example uses this walking algorithm to make certain mobs meander through the world.
mob var/wander New() if(wander) walk_rand(src) ..()
All you have to do to see this in action is define some mobs with the
wander
variable initialized to 1. The algorithm works best with some
edges to follow, so a maze-like map with many tunnels and rooms with walls
is ideal.
In the simplest scenario, designing the world map is merely a matter of
selecting object types which were defined in the code and dropping them onto
the map. The individual objects on the map are called instances of
the object types and as a group are referred to as the initial map population.
Depending on your preference, it is possible to do little or all of the map
design from the code. By creating turfs and other objects with
new() part or all of the map could be generated at run-time. Since
this is a rather cumbersome method, it is usually reserved for cases where
the map is laid out according to some algorithm. For example, a maze-like
map could be randomly generated so that it is different each time it is
played.
Map generating algorithms are an interesting topic, but the techniques
involved are fairly abstract and rely very little on the particulars of the
DM language. For an example, refer to the DM Code Library. One of the
useful items to be found there is the "Amazing Maze Generator," which can
make seemingly infinite dungeons and the like.
In most situations, map design is done principally in the map editor. It is
even possible to go beyond simply using pre-defined object types. Using the
map editor's instance editing feature, you can modify individual objects to
suit your own purposes. This allows one to avoid cluttering up the code
with object types which are really just minor variations of a more general
type but which are required to make instances on the map.
Using the map editor to its fullest potential, one can write code which is
fairly general and independent of the map. This is especially convenient
when the programmer and map designer are different people. In fact, the
code can be written once and used to design many different maps. This
process is referred to as writing a world code base which is then used
to create an endless variety of world instances. These could be
networked together using techniques described in section 12.6.
The map instance editor allows one to modify the object's variables. For
example, you can change the name of an object, its icon, its density, and so
forth. There is no limitation to built-in variables; those defined in the
code may also be modified. In this case you may wish to specify what sort
of value may be given to the variable. This prevents mistakes and also
helps inform the map designer about a variable without requiring the poor
fellow to read the code.
3. Programming for Map Design
|
The valid input types include all those which may be used in a verb argument definition. These are described in section 4.5.1. The list of possible values can be a list of constants or a range of integers. The following example demonstrates both possibilities.
mob/var wealth as num strength in 1 to 100 //defaults to "as num" alignment in list("good","bad","neutral") background as text
Dream Maker's instance editor is a powerful tool, if used appropriately. It can allow you to quickly make unique entities without having to derive new classes for everything. It is also quite simple to use, requiring no other coding knowledge than the ability to fill out forms. Consider the previous code:
mob/var wealth as num strength in 1 to 100 alignment in list("good","bad","neutral") background as text
Now suppose you have derived a monster type from this, say,
/mob/goblin
. Using the instance editor, you can place a bunch of
unique goblins on the map without having to modify the DM code one bit:
goblin
type in the tree.
strength
property must be
a value between 1 and 100. If it is not in this range, it must be reset. In
this fashion, you can change names, descriptions, icons, and so on. You can
make a horde of goblins, each with unique identifications and traits.
locate("chief goblin")
.