DM Guide 4

Версия от 03:38, 19 июня 2014; Epicus (обсуждение | вклад) (Просто перенос на английском для перевода.)
(разн.) ← Предыдущая версия | Текущая версия (разн.) | Следующая версия → (разн.)

Это 4 глава перевода оригинального руководства по Dream Maker от разработчиков.

Остальные главы руководства и статьи о программировании на коде BYOND от любителей на русском.

Оригинальная 4 глава руководства на английском языке

Глава 4: Verbs

It might be only a dream after all, part and parcel of this magic house of dreams. -- L.M. Montgomery, Anne's House of Dreams

Players have several ways to interact with their world. They can move around with the arrow keys, select objects with the mouse, and type commands (or select them from a menu). In DM, the commands you define for players to use are called verbs. They form a sort of player language.

Defining a Verb

Verbs are always attached to some object. This is done by putting the verb inside the object definition. This object is called the source of the verb. The simplest case, is a verb attached to the player's mob.

mob
   verb
      intangible()
         density = 0  //now we can walk through walls!

This example defines a verb called intangible. A player who has executed this command can walk through other dense objects like walls.

As you can see, the definition goes inside a node called verb and is followed by parentheses. The name of the verb itself (like any node) follows the same rules as an object type name. It is case sensitive, consists of letters, digits, and underscores, and must not start with a digit.

Setting Verb Properties

Just as with object types, you can override the actual name of the verb (as seen by the user) to overcome the limitations of the node name. This is done with the set command. Example:

obj/lamp/verb/Break()
   set name = "break"
   luminosity = 0

The reason the verb node could not be directly specified in lowercase is that there is a reserved word break that prevents this. Capitalization is a simple way to avoid conflicts with reserved words because they never begin with a capital letter. (If you are curious about the meaning of break hang on until chapter 6.)

Notice the condensed style using slashes in place of stair-step indentation. The use of the set command distinguishes between an assignment of the object's name and an assignment of the verb's name. Also note that the name assignment is said to take place at compile-time rather than run-time. Otherwise, like the assignment of luminosity, it would not happen until the verb was executed by the player.

There are other properties of a verb that can be configured using the set command. They are presented in the following list.

  • name

As you have already seen, this is the name of the verb as seen by the user. It defaults to the node name with any underscores replaced by spaces.

  • desc

A description of the verb. Players can see this by typing the verb name and pressing F1. Another way is to hold the mouse over it in the verb panel. The syntax is described for you, by default, but if you wish to override the way individual arguments appear, you can do so inside parentheses at the beginning of the text. See page [4.5.1] for an example.

  • category

Verbs can be grouped by assigning a category name of your choosing. They will show up accordingly in the verb panels.

  • hidden

This is 1 or 0 to indicate if the verb is secret. A hidden verb will not appear in the panels and will not be expanded on the command line. As a slight variation of this, verbs beginning with a dot are like hidden verbs except they expand once at least the dot has been typed. You might, for example, have a large number of social commands that you don't want cluttering up the normal verb menus. In that case you would probably want to use the dot prefix to hide them.

  • src

This is where you define the relationship between the user and the source of the verb. The effect is to control who has access to the verb. See the explanation in the next section.


Verb Accessibility

One of the most important aspects of a verb is what it is attached to and who may use it. The intangibility verb seen earlier in this chapter is attached to a mob and accessible to the mob's player alone. You can use your own intangibility verb and other people can use their own intangibility verbs, but you can't use each other's verbs. If you could, it would be possible to turn each other intangible.

Of course, in some cases, you might actually want people to be able to use each other's verbs. To do that, you need to override the default accessibility settings. Before we get into that, you need to understand how the defaults work.

When a verb is attached to a mob, the default accessibility setting is set src = usr. That means the source of the verb must be equal to the user of the verb. Nobody else is allowed to use it (or even see it). This statement makes use of two special pre-defined variables. The variable src refers to the object containing the verb--that is, the source object. The variable usr refers to the mob (of the player) who is using the verb.

Here is a commonly used verb which allows people to turn each other into potatoes.

mob/verb/make_potato()
   set src in view()
   name = "potato"
   desc = "Mmm.  Where's the sour cream?"
   icon = 'potato.dmi'

Instead of the default accessibility (src = usr) this verb uses src in view(). That means anyone within view of the user can be turned into a potato. The view procedure computes a list of everything which is visible to the user.

Also note that in this example an underscore was used in the name of the verb. This will be automatically converted into a space in the name of the command. However, when the user types it in, a `-' will be inserted on the command line instead of a space. That is because spaces are only allowed on the command line between arguments or inside of quotes. The user doesn't have to worry--the substitution of a dash happens automatically.

Consider instead a verb that is attached to an obj.

obj/torch/verb/extinguish()
   set src in view(1)
   luminosity = 0

The verb extinguish in this example causes a torch to stop shining. It again makes use of the view instruction, but this time an additional parameter is specified to limit the range. In this case, the range is 1, so the torch must be in the user's turf or an adjacent one for the verb to be accessible. Somebody standing across the room will not be able to extinguish the torch.

Try this example on a suitable map with some torches scattered about. If you move up to a torch and type "extinguish torch" it will go out (see figure 4.5).


Figure 4.5: Look Ma, no hands! (and other ways to avoid typing...)

You need not worry about players having to deal with typing lengthy commands. Dream Seeker provides many convenient ways to ease the burden on the users' fingers. For example, there are several methods for a player to access the aforementioned extinguish torch verb:

Type "extinguish torch". Type the first few letters of the command, for instance, "ex" and then hit the spacebar to expand the command. Dream Seeker will fill in the remaining letters, indicating ambiguity on-screen. Click on the "extinguish" entry in the on-screen verb panels. Right-click on the torch to pull up a context-menu from which the extinguish verb may be selected.


You may have noticed a subtle difference between the extinguish verb and the intangible verb seen earlier. In one case we just had to type "intangible" and in the other "extinguish torch". In the first case we didn't have to specify the verb source and in the second case we did. The term for this difference is an implicit versus an explicit source.


Explicit versus Implicit Source

Suppose one practices a religion in which praying must be done in the vicinity of a torch. We don't want the command to be "pray torch" but just "pray". In other words, we want an implicit source, not an explicit one.

Whether the source syntax is implicit or explicit depends on how the src setting is specified. If src is assigned (e.g. set src=usr or even set src=view(1)) the computer automatically picks the source from the available possibilities. On the other hand, if src is not assigned but just limited to anything in a list (e.g. set src in view(1)) it is up to the user to specify the source--even if there is only one choice. This gives the designer control over the command syntax.

Return to the example of torch enabled prayers. Since we want an implicit source, we use = to assign the source rather than in, which would merely limit it.

obj/torch/verb/pray()
   set src = view(1)
   //God fills in this part!

In general, one uses an implicit source when the mere presence of an object gives the user some ability that is otherwise independent of the source object. Another case (like set src=usr) is when the source object is always unique. Verbs in this latter case are called private verbs because they are only accessible to the mob itself.

Default Accessibility

For convenience, verbs attached to different object types have different default accessibilities. These are summarized in figure 4.6.

Figure 4.6: Default Verb Accessibilities

mob src = usr obj src in usr turf src = view(0) area src = view(0)

Note that the default obj accessibility is really an abbreviation for src in usr.contents, which means the contents (or inventory) of the user's mob. As you shall see later on, the in operator always treats the right-hand side as a list--hence usr is treated as usr.contents in this context.

In the case of both turf and area, the default accessibility is view(0) and is implicit. Since the range is zero, this gives the player access to the verbs of the turf and area in which the mob is standing.

Making use of this convenient default, suppose we wanted a dark area to have a magical trigger that would turn on the lights at the sound of clapping hands. You could do it like this:

area/dark/verb/clap()
   luminosity = 1

Abracadabra!


Possible Access Settings

There are a limited number of possible settings for a verb's source access list. They are compiled in figure 4.7.

Figure 4.7: Possible Source Access Settings

usr usr.loc usr.contents usr.group view() oview() Two of these (namely, usr and usr.loc) are not lists of objects but instead refer to an individual item. For that reason, they behave a little differently when used in an assignment versus an in clause. Don't let that confuse you--it's really quite simple. When used in an assignment, they are treated as a single object. When used with in they represent the list of objects that they contain.

The rest of the src access settings are all lists. The view instruction has already been mentioned, but it requires a little more description so that you can understand the related oview instruction.

The view() list contains all objects seen by the user up to a specified distance. The list starts with objects in the user's inventory, followed by the user herself, objects at her feet, then in neighboring squares, and so forth proceeding radially outward. A distance of 0 therefore includes the turf (and area) the user is standing on and its contents. A distance of 1 adds the neighboring eight squares and their contents. The maximum distance is 5, since that includes the entire visible map on the player's screen. (You will see how to change the map viewport size in chapter 14.) Because this is often the desired range, it is the default when no range is specified. The special range of -1 includes only the user and contents.

The related instruction oview() stands for `other' or `outside' view. It is identical to view() except it doesn't include the usr or the usr's contents. In other words, it excludes objects in view(-1), the so-called private or personal range.

As an example of using usr and usr.loc, consider a pair of verbs to allow picking up and dropping objects.

obj/verb
   get()
      set src in usr.loc
      loc = usr
   drop()
      set src in usr  //actually this is the default
      loc = usr.loc

To see how oview() might come in handy, suppose there was a magical torch that one could summon from a greater distance than provided by the standard get verb.

obj/torch/verb/summon()
   set src in oview()
   loc = usr  //presto!

Making use of the default range, torches can be summoned from anywhere in the player's view. If we had used view() instead of oview(), objects already inside the user's inventory could be summoned, which wouldn't make much sense.


Overriding Verbs

Suppose instead of a magical summon verb, we just wanted the get verb to have a greater range for torches. This is an example of object-oriented programming in which a child object type provides the same sort of operations as its parent but implements them differently.

Here is the code for such a modified get verb.

obj
   verb
      get()
         set src in usr.loc
         loc = usr
      drop()
         set src in usr
         loc = usr.loc
   torch
      get() //extended range
         set src in oview()
         loc = usr

The example is written in full to demonstrate the syntax for overriding a previously defined verb. Notice that inside torch, we do not put get() under a verb node. This indicates to the compiler that we are overriding an existing verb rather than defining a new one.

The difference in the syntax for definition verses re-definition serves the purpose (among other things) of preventing errors that might happen in large projects. For example, you might define a new verb that mistakenly has the same name as an existing one, or you might try to override an existing verb but misspell it in the re-definition. In both of these cases the compiler will issue an error, preventing a problem that might otherwise go unnoticed for quite some time.


Friendly Arguments

Verbs become much more powerful when you add the ability to take additional input from the user. A programmer would call this a verb parameter or argument. You can define as many arguments to a verb as you wish. However, most verbs only take one or two parameters.

Arguments are each assigned a different variable name inside the parentheses of a verb definition. In addition to this, an input type must be specified. This indicates what sort of information is required from the player. A generic verb definition would therefore look like this:

VerbName(Var1 as Type1,Var2 as Type2,...) The variable names follow the same rules as everything else in the language. Case matters, and it may consist of letters, numbers, and the underscore.


Parameter Input Types

The possible input types are listed in figure 4.8.

Figure 4.8: Parameter Input Types

text message num icon sound file key null mob obj turf area anything The first group are the constant input types. They all represent different types of data that the user can insert. They may be used individually or in combination. To combine several types, put | between them like this: icon|sound.

The text input type accepts a short (one-line) string from the player. For a longer composition, the message input type is used.

Numbers are handled by the num input type. These, just like numbers in the language, may be positive or negative, integer or floating point, and may even be specified in scientific notation.

There are three input types for resource files: icon, sound, and file. The last one, file, will take any type of file as an argument, whereas the other two take only icons and sounds, respectively. The related input type key takes a key entry from the player and is only used in obscure situations.

The null input type is used in conjunction with other types. It indicates that the argument is optional. Otherwise, the user is required to enter a value before executing the command.

The last group are the object input types. They are used to allow the player to select an item from a list of objects. More will be said on lists of objects in section 4.8. By default, the list is composed of all objects in view of the player.

Using the various input types, it is possible to compose verbs that give the player control over his own appearance. For example, using the text input type, the name can be specified.


mob/verb/set_name(N as text)
   set desc = "(\"new name\") Change your name."
   name = N

In the client, one could therefore enter a command like the following:

set-name "Dan The Man Notice in this example that the help text for the verb has been defined. First the syntax is described in parentheses and then the purpose of the command is stated. (The reason there are backslashes in front of the double quotes inside the text is to prevent them from being mistaken for the end of the description. This is called escaping and will be discussed in more detail in section 11.3.2.) If you position the mouse over the verb, the help text will be displayed. It will look something like this:

usage: set-name "new name" (Change your name.) If we had not specified the syntax help (in parentheses), it would have given a generic description of the syntax like this:

usage: set-name "text" (Change your name.) Each type of argument has a different default appearance. Generally speaking, it involves the name of the input type. If you think that will confuse the players, override it with your own text.

As a slight variation on the previous example, we could make a scroll object on which one can write a message.

obj/scroll/verb
   write(msg as message)
      set src in view(0)
      desc = msg
   read()
      set src in view()
      usr << desc

Notice that players must be within arm's reach to inscribe a message. We assume that everyone has good eyesight so the complementary read command works as long as the scroll is within view.

It is amazingly simple to do for the player's icon what we just did for the description. Here is a verb that does the trick.

mob/verb/set_icon(i as icon)
   set name = "set icon"
   icon = i

The command on the client could be issued something like this:

set-icon 'me.dmi Here, single quotes surround the file name. As with text arguments, the final quote is optional when there are no additional parameters.


Generating Output

The simplicity of the set_icon verb hides the power behind it. Think about what happens when you use it. You enter the name of an icon file through the client and it magically appears on the map. In a game running over the network with multiple users, it works just the same. Behind the scenes, the file you specify gets transported to the server, which then automatically distributes it to all the other clients. Transmitting multi-media files across the network is an elementary operation in DM, little different than entering some numbers or text.

Speaking of multi-media, here is an example that makes use of the sound input type.

mob/verb/play(snd as sound)
   view() << snd

This plays a sound file (either wav or midi) to everyone in view. The << operator in this context is used to send output to a mob or list of mobs. In this case, everyone in the list computed by the view() instruction receives a sound file. (To be precise, only those people who need a copy of the sound file will receive it. If they don't have sound turned on or if they already have the file, it won't be transmitted.) If their machine is capable of playing sounds and their client is configured to allow it, the sound will automatically play for them. Not bad for two lines of code!

The output operator << opens up all sorts of possibilities. For example, you can say things.

mob/verb/say(msg as text)
   view() << msg

Variables in Text

Usually, however, you would want some indication of who is doing the talking. To do that, you need a new piece of magic called an embedded text expression. It allows you to display text containing variables.

mob/verb/say(msg as text)
   view() << "[usr] says, '[msg]'"

In this example, the variables usr and msg are substituted into the text before it is displayed. The result could be something like "Ug says, 'gimme back my club!'". As you can see, the brackets [ ] inside of the text are used to surround the variables. Such a construct is called an embedded expression. It is buried right inside the text but it gets replaced at run-time by its value. More details will be revealed on that subject in section 11.3.

We can now make use of the object input types. For example, you can wink at people with the following verb.

mob/verb/wink(M as mob)
   view() << "[usr] winks at [M]."

The possibilities for intrigue and secrecy increase with a covert version of the say command.

mob/verb/whisper(M as mob,msg as text)
   M << "[usr] whispers, '[msg]'"

This example uses two arguments to achieve its purpose. The first one is the target of the message. The second is the text to transmit.


Flexibility in Choice of the Source

If you are paying close attention, a thought may have occurred to you. Couldn't these verbs that take an object as an argument be written using that object as the source rather than an argument? The answer is yes. For example, wink could be rewritten like this:

mob/verb/wink()
   set src in view()
   view() << "[usr] winks at [src]."

Instead of taking a mob as an argument, this verb defines a public verb on mobs that is accessible to others in view, allowing them to wink at the mob. From the user's point of view, these two cases are identical. From the programmer's view, however, it is sometimes more convenient to use one technique over the other.

Suppose you wanted to make a special type of mob that when winked at would reveal a magic word. In that case, the best way to do things would be to have the target of the wink be the source of the verb. Then you can override the wink verb for the special mob type like this:

mob/guard/wink()
   ..()
   usr << "[src] whispers, 'drow cigam!'"

When winked at, the guard mob whispers back the magic word. Notice the line that executes the .. (dot-dot) procedure. That is a special name that corresponds to the previous (inherited) version of a verb (called the parent or super verb). This saved us from having to rewrite the line that generated the wink output. That is what is meant by re-usable code in object-oriented programming. With complicated procedures it can save you a lot of trouble.

If, instead, we had used the original version of wink which had the target mob as an argument, we would have had to insert code in the general wink verb to check if the target was a guard and act accordingly. In general, good object-oriented design attempts to confine code that is specific to a particular type of object inside the definition of that object. This was achieved in the above example by making the target the source of the wink verb.

In a different situation, the reverse might be the best strategy. For example, you might want a special mob who kills people by winking at them. (If looks could kill...)

mob/DM/wink(M as mob)
   set desc = "This kills people, so be careful!"
   ..()
   del M  //poof!

To do the nasty deed, we used the del instruction, which deletes an object. In this case, we have assumed the existence of the first definition of wink() which takes a mob argument. By organizing things this way, we were able to isolate the special code to the object it applied to, in this case the DM.

Of course you might have even more complicated scenarios in which you want to do both variations--that is, having code specific to the type of target and the user. It can still be handled without violating good object-oriented design, but you would need some tools I haven't fully described yet. (For example, you could define a second procedure that carries out a mob's response to being winked at and invoke that from within a private wink verb.)

However, don't get too carried away trying to blindly adhere to object-oriented or any other philosophy of code design. At the root of all such theories is the basic and more important principle of keeping things simple. The simpler things are, the less likely you are to make mistakes and the easier it is to fix the errors you do make.

The reason the object-oriented approach tries to confine code about an object to that object is ultimately just organizational. When you want to change the magic word spoken by the guard, you know where to go in the code to do it. And more importantly, if you want to change the guard to an elf, you don't have to remember to also go and modify the wink verb to make elves speak the magic word rather than guards--a seemingly unrelated task.

But such possibilities are hypothetical and should not take precedence over your own knowledge about what future developments are actually probable. In some situations, the simplest structure might be to confine all code having to do with winking to one place--a single wink verb that handles every special case. That would be procedure-oriented programming, an aged methodology (though tried and true). But who cares what the theory is. Get the job done with clear, concise code. That's what matters in the end.

(Of course every true programmer knows that there is no such thing as an end. At best there is a level of completeness that one approaches asymptotically. At worst ... well, never mind the worst, those dark skeletons, cadaverous parasitic programs that suck at the soul until one yields again, hammering out another thousand lines of tangled spaghetti code, writhing like a nest of tapeworms, and long sleepless nights turn into weeks, years, and still no sign of completion! Or so I am told. Being one of the cheery daytime programmers, in bed before midnight and up to milk at dawn, I wouldn't know whether such horror stories are true. If it happens to you, I can give a few pointers on keeping a cow.)


A Choice of Arguments

Notice how in the previous section, there was a slight asymmetry in the two versions of wink. In one case the target was a mob argument and in the other it was the source. In the latter case, we specified that the source could be anywhere in view of the user, but in the case of the argument we never said anything about the range. What if we wanted to restrict winking to a shorter distance in that case?

As it happens, arguments can be limited in much the same way as the source of a verb.

VerbName(Var1 as Type1 in List1,Var2 as Type2 in List2,...) For example, here is a verb for prodding your neighbor.

mob/verb/poke(M as mob in view(1))
   view() << "[usr] pokes [M]!"

The use of the in clause in the argument definition limits the user's choice to mobs in neighboring positions. You can use any list expression to define the set of possible values. The most common ones are those available for defining the range of a verb source (section 4.3.3). When no in clause is used, the default list for mob, obj, turf, and area input types is view().

For example, here is a verb to communicate (by frequency modulated electromagnetic waves) with any other player in the game.

mob/verb/commune(M as mob in world,txt as text)
   M << "[usr] communes to you, '[txt]'"

Actually, world is an individual object, but in this context it is treated as a list and is therefore an abbreviation for world.contents, a list of every object in the game.


Default Arguments

Verb arguments can be made optional by using the null input type. If the user does not enter a value for the argument, it will be given the special value null. In this case, one will often need to check if the argument is indeed null and handle things accordingly. This can be automated in the case where you just want a default value to be substituted for null by assigning the default value in the variable definition.

The most general syntax for an argument definition contains the variable name, a default value, an input type, and a list of possible values.

variable = default-value as input-type in list If a default value is specified, the null input type is automatically applied, since that is necessary to make the argument optional.

mob/DM
   verb
      set_density(d=1 as num)
         set name = "set density"
         density = d

This example defines a verb that controls the player's density. If no arguments are given, the mob will be made dense; otherwise the value specified by the player will be used.

anything input type

The anything input type allows you to combine other input types with items from a list. Its purpose is to make clear the fact that the constant input types are in addition to whatever may be in the object list.

The following example allows you to change your icon, with the option of selecting the icon from a file or from any other object in view.

mob/verb/set_icon(I as icon|anything in view())
   icon = I

Note that this happens to work because assigning an object to the icon variable causes that object's icon to be assigned instead. (That behavior exists because I did not want to introduce conditional statements yet. Such are the hoops a programmer will jump through to avoid extra documentation!)