DM Guide 14: различия между версиями
Dnivji (обсуждение | вклад) (Залил) |
(нет различий)
|
Версия от 23:11, 16 апреля 2015
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.
1. Spatial Instructions
There are a number of instructions for working with coordinates and objects on the map. When you want to produce some sort of spatial effect, these are the building blocks you will need.
1.1 view list
The view instruction returns a list of all visible objects. It is often used without any arguments, but may take a range and center object as parameters.
view (Range=world.view,Center=usr) Range is the maximum distance to look. Center is the central object in the view. Returns a list of visible objects. 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.
Figure 14.24: Viewing Range
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.
1.2 oview list
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 view(-1). This is most often useful when broadcasting a message to everyone in view except the perpetrator of some action.
1.2.1 Point of View
When describing events in the game to players, one may stick strictly to third person or use a mixture of second and third person. For example, when a player smiles, everyone could see the text "[usr] smiles", or everyone but the player could see that and the player could instead see "You smile".
The advantage of a strict third person point of view is simplicity. It tends to have the feel of a story in which the player's mob is one of the characters. A second person point of view, on the other hand, is less like a story and more like a play in which the player is actively taking one of the roles. It's up to you which effect you want.
Strict 3rd person output can simply be generated by broadcasting to all in view(). A second person point of view would instead use oview(). The following examples contrast the two methods.
//3rd person description mob/verb/smile()
view() << "[usr] smiles."
//2nd person description mob/verb/smile()
usr << "You smile." oview() << "[usr] smiles."
Commonly, actions involve three groups of people: one who performs an action, another who is acted upon, and everyone else. In a second person system, the first two people would get a specially tailored second person description and everyone else would get a third person description.
The following example demonstrates how this could be achieved.
mob/verb/smile(M as mob|null)
if(!M) //no target mob specified usr << "You smile." oview() << "[usr] smiles." else usr << "You smile at [M]." M << "[usr] smiles at you." oview() - M << "[usr] smiles at [M]."
Recall that the - operator produces a list with the specified item removed. If that was not used in this example, the mob being smiled at would see the third person description as well.
1.3 range and orange instructions
The range instruction is exactly like view except it ignores visibility. All objects within the specified range are included in the list whether they are visible or not.
Similarly, the orange instruction behaves like oview except it also ignores visibility. It returns the same list as range minus the central object and its contents.
(In case you use it in conversation (I do all the time) it is pronounced `oh-range' and not `orange' like the color. Still, it might be useful to poets in search of a rhyme. Her hair was orange\ And she was in my oh-range...)
range (Range=world.view,Center=usr) orange (Range=world.view,Center=usr) Range is the maximum distance to look. Center is the central object in the view. Returns a list of objects within range.
1.4 locate instruction
The locate instruction is used to get the reference of an object by specifying some unique property of the object. In the case of turfs, the map coordinates may be given. The object type, tag name, and \ref value may also be used to identify the desired object.
locate (x,y,z) locate (Type) in Container locate (Tag) in Container x,y,z are turf coordinates. Type is the object type. Tag is a unique text string identifier. Container is the optional location or list. Returns the object or null if none. 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.
1.5 block instruction
The block instruction generates a list of all turfs in a rectangular section on the map.
block (SW,NE) SW is the south-west turf. NE is the north-east turf. Returns a list of turfs in the block. 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.
1.6 get_dist instruction
The get_dist instruction determines the distance between two locations. The distance is the same as the range parameter to view(). If the objects are in the same place, it is 0. If they are in neighboring positions, it is 1, and so on. (Note that get_dist does not compute the geometric Euclidean distance. It treats diagonal movements as equal to straight ones.)
get_dist (Loc1,Loc2) Loc1 is the first location. Loc2 is the second location. Returns the separating distance. 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.
2. Movement
There are a number of instructions relating to movement of objects on the map. They are listed in figure 14.25.
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.
2.1 walk instruction
The walk, step, and get_step instructions are for movement in a fixed direction.
walk (Obj,Dir,Lag=0) step (Obj,Dir) get_step (Obj,Dir) Obj is the object to move. Dir is the direction to move. Lag is the delay between steps in 10$^th$s of seconds. 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.
2.1.1 get_dir instruction
The get_dir instruction computes the direction from one location to another. If the true direction is not exactly equal to one of the eight standard directions (NORTH, SOUTH, and so on), the closest one will be chosen.
get_dir (Loc1,Loc2) Loc1 is the first location. Loc2 is the second location. Returns the direction from Loc1 to Loc2.
2.1.2 turn instruction
The turn instruction rotates a direction by the specified amount.
turn (Dir,Angle) Dir is the initial direction. Angle is the angle to rotate. Returns the new direction. 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!
2.2 walk_towards
The walk_towards, step_towards, and get_step_towards instructions move in the direction of another object. If the target object changes position, the walking algorithm automatically adjusts the direction of motion accordingly.
walk_towards (Obj,Targ,Lag=0) step_towards (Obj,Targ) get_step_towards (Obj,Targ) Obj is the object to be moved. Targ is the destination. Lag is the delay between steps in 10$^th$s of seconds. 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.
2.3 walk_to instruction
The walk_to, step_to, and get_step_to instructions move to another target object, taking intervening objects into account and attempting to intelligently maneuver around them if possible. If the target is too far away (more than the width of the map view), no action is taken.
walk_to (Obj,Targ,Dist=0,Lag=0) step_to (Obj,Targ,Dist=0) get_step_to (Obj,Targ,Dist=0) Obj is the object to be moved. Targ is the destination. Dist is the maximum desired distance. Lag is the delay between steps in 10$^th$s of seconds. 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.
2.4 walk_away instruction
The walk_away, step_away, and get_step_away instructions move to another target object, taking intervening objects into account and attempting to intelligently maneuver around them if possible.
walk_away (Obj,Targ,Dist=world.view,Lag=0) step_away (Obj,Targ,Dist=world.view) get_step_away (Obj,Targ,Dist=world.view) Obj is the object to be moved. Targ is the destination. Dist is the minimum desired distance. Lag is the delay between steps in 10$^th$s of seconds. An example using this algorithm is a command to run away from someone.
mob/verb/flee(mob/M)
walk_away(src,M,5,30)
2.5 walk_rand instruction
The walk_rand, step_rand, and get_step_rand instructions generate seemingly random motion. The object being moved appears to wander aimlessly. That is different from wandering mindlessly, which would be the result of random movement. In fact, this algorithm is not very random but instead uses a technique known as edge following to create the effect of purposeful motion.
walk_rand (Obj,Lag=0) step_rand (Obj) get_step_rand (Obj) Obj is the object to be moved. Lag is the delay between steps in 10$^th$s of seconds. 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.
3. Programming for Map Design
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.1 Variable Input Parameters
Object variables may be declared in much the same way as verb arguments to provide extra information to the map editor. Both the input type and a list of possible values may be specified. Otherwise a variable simply defaults to accept any type of value.
var/VarName as Type in List VarName is the variable being defined. Type is the input type. List is the list of possible values. 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
Figure 14.26: Hands-free programming!
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:
Select the goblin type in the tree. Click on the Edit button in the adjacent panel. This will bring up a dialog with the properties of the goblin. Modify these properties by changing the values in the form. If you enter invalid values, the editor will correct you. It does this by looking at the specified filters. For instance, the 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. One particular useful property is the tag. This is just a text string that can be used to distinguish instances in the locate() instruction. So if you want to make a "chief goblin" without deriving a new type, you might want to set the tag to "chief goblin" and set the other properties accordingly. You can then locate this goblin later on by doing locate("chief goblin"). After you are done editing the goblins, place them on the map by selecting the corresponding instance in the panel (they will be sorted by tag). If you want to re-edit them later, you can use the Look option and proceed from there.