DM Guide 4
Это 4 глава перевода оригинального руководства по Dream Maker от разработчиков.
Остальные главы руководства и статьи о программировании на коде BYOND от любителей на русском.
Оригинальная 4 глава руководства на английском языке
В разработке… |
Данная статья помечена как неоконченная. Это означает, что статья находится на доработке, поэтому является неверной или неактуальной. Вы можете помочь проекту Onyx и сообществу SS13 в целом — зайдите на наш Портал сообщества. |
Глава 4: Действия verb
В конце концов, это может быть всего лишь сон. Только часть и лишь один слой этого пристанища грез. — Л.М. Монтогомери, Пристанище снов Анны.
У игроков есть несколько способов взаимодействовать с окружающим миром: они могут передвигаться с помощью стрелок на клавиатуре, выбирать объекты с помощью мыши и вводить команды (или выбирать их в меню). В DM команда, которую вы создаете для использования игроками, называется verb (действие). Они составляют язык общения игроков с сервером.
Создание действия
Действия всегда задаются для объектов путем определения этого действия внутри описания объекта. Такой объект называют источником действия (source). Примитивный случай: действие, привязанное к существу игрока.
mob verb intangible() density = 0 //now we can walk through walls!
Этот пример описывает действие "нематериальный" (intangible). Игрок, который использует его, получает возможность передвигаться сквозь материальные объекты, вроде стен.
Как вы, наверное, заметили, определение записывается под узлом verb и сопровождается круглыми скобками. Само название действия, как и любой другой узел в вашей программе, должно удовлетворять некоторым условиям: оно чувствительно к регистру, может состоять из букв, цифр и подчеркиваний, а также не должно начинаться с цифры.
Свойства описания действия
Также, как и с объектами, вы можете задать новое имя для действия (видимое пользователем), чтобы обойти ограничения для названий узлов. Это можно сделать с помощью специальной команды:
obj/lamp/verb/Break() set name = "break" luminosity = 0
Причина того, что этот узел не может быть задан в нижнем регистре, лежит в том, что слово break зарезервировано. Озаглавливание - простой способ избежать конфликта с зарезервированным словом, поскольку они никогда не начинаются с большой буквы. Если вас озадачило значение break, дождитесь 6 главы.
Обратите внимание на использование множества слешей, заместо разделения отступами. Команда set по-разному используется для присвоения имен объектов и действий. Также заметьте, что присвоение имени происходит скорее во время компиляции, чем во время работы программы, хотя оно и не должно происходить до тех пор, пока игрок не вызовет действие, как и изменение переменной освещения, например.
Также существуют и другие свойства действий, описанные в следующем списке:
• name - как вы уже поняли, название действия, видимое пользователем. По умолчанию, оно соответствует названию узла действия, с заменой нижних подчеркиваний на пробелы.
• desc - описание действия. Игроки могут увидеть его, если введут название действия и нажмут F1 или просто наведут указатель мыши на это действие в меню. Синтаксис описания стандартный, но если вы захотите изменить способ появления новых аргументов, то вы можете сделать это в скобочках в начале текста. Смотри страницу [4.5.1] для примера.
• category - действия могут группироваться по категориям, имена для которых вы задаете этим свойством. Категории появляются на панели действий.
• hidden - может принимать значения 1 или 0, чтобы определить скрытые действия. Такие действия не появляются на панели или в меню. Как вариант облегченной версии спрятанных действий, вы можете начать их название с точки. Тогда они будут работать как скрытые, но появятся в меню, если ввести точку. Вы можете, например, использовать их, чтобы задать кучу социальных действий, которыми вы не хотели бы захламлять меню.
• src - описывает, где вы определяете отношения между пользователем и источником действия. Влияет на то, кто имеет контроль к действию. Смотри объяснение в следующей секции.
Доступ к действиям
Одни из самых важных аспектов действий - это то, к чему они прикреплены и кто их может использовать. Действие "нематериальный", которое мы рассматривали ранее в этой главе, прикреплено к существу и доступно только для игрока, который управляет этим существом. Вы можете использовать свое собственное действие нематериальности, а другие игроки могут использовать свои, такие же действия, но вы не можете использовать их друг за друга.
Конечно, в некоторых случаях, вы захотите разрешить людям использовать действия друг друга. Чтобы это сделать, вам нужно будет переписать стандартные настройки доступа. Перед тем, как приступить к этому, вам нужно понять как работают настройки по умолчанию.
Когда действие прикреплено к существу, автоматические настройки ставятся в положение src = usr. Это означает, что источник действия (source) должен быть эквивалентен тому, кто использует это действие (user). Никто больше не может их использовать или даже видеть. Этот оператор использует две предопределенные переменные: src обращается к объекту, который хранит действие, источнику (source), а usr обращается к существу, которое использует действие (user).
Это самое обычное действие, которое позволяет людям превращать друг друга в картошку:
mob/verb/make_potato() set src in view() name = "potato" desc = "Mmm. Where's the sour cream?" icon = 'potato.dmi'
Вместо стандартных настроек доступа (src = usr), это действие использует src in view(). Это означает, что любой в поле зрения пользователя может быть превращен в картошку. Процедура view возвращает список всего, что находится в поле зрения того, кто ее использует.
Также обратите внимание на то, что в имени действия использовалось нижнее подчеркивание. Оно автоматически превратится в пробел в имени команды. Однако, если пользователь хочет ввести эту команду, то ему нужно вводить дефис "-", а не пробел. Это связанно с тем, что пробелы в командной строке могут использоваться только между аргументами или внутри ковычек. Пользователю не стоит волноваться, замена дефисов происходит автоматически.
Теперь рассмотрим действие, привязанное к объекту:
obj/torch/verb/extinguish() set src in view(1) luminosity = 0
Мам, смотри, без рук!
(и другие способы избежать набора...)
Вам не нужно волноваться об игроках, которым приходится набирать длинные команды. Dream Seeker предусматривает множество удобных путей облегчить жизнь пальцам пользователей. Вот несколько способов использовать вышеупомянутое действие тушения факела:
- Ввести "extinguish torch".
- Ввести первую пару букв команды, например, "ex" и нажать пробел, чтобы автодополнить ввод. Если что, Dream Seeker укажет вам на двусмысленность специальным окном.
- Кликнуть по "extinguish" на панели действий.
- Щелкнуть правой кнопкой мыши по факелу, чтобы вывести контекстное меню и выбрать требуемое действие.
Действие extinguish в этом примере заставляет факел потухнуть. Мы опять используем view, но на этот раз вместе с дополнительным параметром, которое указывает на дальность действия. В этом случае, с дальностью 1, факел должен быть или на том же месте, что и существо, использующее действие, или на соседней клетке. Кто-нибудь в другом конце комнаты не получит доступа к этому действию и потушить факел не сможет.
Опробуйте этот пример на подходящей карте с несколькими факелами, разбросанными вокруг. Если вы подойдете к факелу и наберете "extinguish torch" - он потухнет (смотри вставку).
Вы могли заметить тонкую разницу между действием тушения и нематериальности, которое мы описывали ранее. В одном случае нам нужно ввести лишь "instangible", а в другом "extinguish torch". Дело в том, что в первом случае нам не нужно указывать источник действия поскольку он итак понятен, а во втором нужно.
Неявный источник против Явного
Предположим, что кто-то исповедует религию, в которой молиться необходимо в непосредственной близости от факела. Мы не хотим команду "pray torch", а хотим просто "pray". Другими словами мы хотим использовать неявный источник, вместо явного.
Используем мы явный или неявный источник - зависит от того, как заданы параметры src. Если src однозначно определен (например, вы написали src=usr или даже src=view(1)), компьютер автоматически будет использовать доступный источник. С другой стороны, если src не определен, а просто ограничен определенным списком (например, src in view(1)), то пользователю придется указать источник самому, даже если в списке всего один пункт. Так мы можем контролировать синтаксис вводимой команды.
Вернемся к примеру факела, включающего молитвы. Раз мы хотим неявный источник, то нам нужно использовать оператор "=", а не "in", чтобы ограничить варианты для компьютера.
obj/torch/verb/pray() set src = view(1) //God fills in this part!
В основном, неявное определение источника используют, когда лишь присутствие предмета дает пользователю новые способности, которые, в общем-то не зависят от того, какой конкретно объект их дает. Также их используют, когда объект-источник всегда один и тот же (например, как src=usr). Действия из последнего случая называют личными действия, поскольку они доступны только самому существу.
Доступ по умолчанию
For convenience, verbs attached to different object types have different default accessibilities. These are summarized in figure 4.6.
Настройки действий по умолчанию
- 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!)