DM Guide 4
A long time ago in a galaxy far, far away... |
Данная статья помечена как устаревшая, её содержание может быть неверным или неактуальным. Если она не будет актуализирована или не появится веского повода для снятия этой плашки, то вскоре она будет перемещена в Священный архив. При желании вы можете помочь проекту Onyx и сообществу![]() ![]() |
Это 4 глава перевода оригинального руководства по Dream Maker от разработчиков.
Остальные главы руководства и статьи о программировании на коде BYOND от любителей на русском.
Оригинальная 4 глава руководства на английском языке
В разработке… |
Данная статья помечена как неоконченная. Это означает, что статья находится на доработке, поэтому является неверной или неактуальной. Вы можете помочь проекту |
Глава 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). Действия из последнего случая называют личными действия, поскольку они доступны только самому существу.
Доступ по умолчанию
Для удобства verb, относящиеся к разным типам объектов, имеют разную доступность по умолчанию. Они обобщены на рисунке 4.6.
Настройки действий по умолчанию
- mob src = usr
- obj src in usr
- turf src = view(0)
- area src = view(0)
Обратите внимание, что доступность obj по умолчанию на самом деле является сокращением src в usr.contents, что означает содержимое (или инвентарь) моба пользователя. Как вы увидите позже, оператор in всегда рассматривает правую часть как список - поэтому в данном контексте usr рассматривается как usr.contents.
В случае с постройками(turf) и областью(area) доступность по умолчанию равна view(0) и является неявной. Поскольку диапазон равен нулю, это дает игроку доступ к verb постройки(turf) и области(area), на которой стоит моб.
Используя эту удобную настройку по умолчанию, предположим, что в темном помещении нужно установить магический триггер, который будет включать свет при звуке хлопка в ладоши. Это можно сделать следующим образом:
area/dark/verb/clap() luminosity = 1
Абракадабра!
Возможные настройки доступа
Существует ограниченное количество возможных настроек для списка доступа к источнику глагола. Они приведены на рисунке 4.7.
Рисунок 4.7: Возможные настройки доступа к источнику
usr usr.loc usr.contents usr.group view() oview()
Два из них (а именно usr и usr.loc) не являются списками объектов, а ссылаются на отдельный элемент. По этой причине они ведут себя немного по-другому, когда используются в присваивании и в операторе in. Пусть вас это не смущает - на самом деле все очень просто. При использовании в присваивании они рассматриваются как один объект. При использовании с in они представляют собой список объектов, которые они содержат.
Остальные параметры доступа к src представляют собой списки. Инструкция view уже упоминалась, но она требует более подробного описания, чтобы вы могли понять связанную с ней инструкцию oview.
Список view() содержит все объекты, видимые пользователем на заданном расстоянии. Список начинается с объектов в инвентаре пользователя, затем идет сам пользователь, объекты у его ног, затем в соседних квадратах и так далее по радиусу наружу. Расстояние 0, таким образом, включает в себя постройки(turf) (и область(area)), на котором стоит пользователь, и его содержимое. Расстояние 1 добавляет соседние восемь квадратов и их содержимое. Максимальное расстояние - 5, так как оно включает в себя всю видимую карту на экране игрока. (Как изменить размер области просмотра карты, вы узнаете в главе 14.) Поскольку этот диапазон часто является желаемым, он используется по умолчанию, когда диапазон не указан. Специальный диапазон -1 включает только пользователя и содержимое.
Связанная с ней инструкция oview() означает "другой" или "внешний" вид. Она идентична view(), за исключением того, что не включает в себя usr или содержимое usr. Другими словами, она исключает объекты в view(-1), так называемый приватный или личный диапазон.
В качестве примера использования usr и usr.loc рассмотрим пару verb, позволяющих поднимать и опускать предметы.
obj/verb get() set src in usr.loc loc = usr drop() set src in usr //actually this is the default loc = usr.loc
Чтобы увидеть, как может пригодиться oview(), предположим, что существует магический факел, который можно призвать с большего расстояния, чем позволяет стандартный verb/get.
obj/torch/verb/summon() set src in oview() loc = usr //presto!
Используя диапазон по умолчанию, факелы можно призвать из любого места в поле зрения игрока. Если бы мы использовали view() вместо oview(), можно было бы вызывать объекты, уже находящиеся в инвентаре пользователя, что не имело бы особого смысла.
Переопределение verb
Предположим, что вместо магического verb/summon мы просто хотим, чтобы verb/get имел больший диапазон для факелов. Это пример объектно-ориентированного программирования, в котором дочерний тип объекта предоставляет те же операции, что и его родитель, но реализует их по-другому.
Вот код для такого модифицированного verb/get.
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
Пример написан полностью, чтобы продемонстрировать синтаксис переопределения ранее определенного verb. Обратите внимание, что внутри "torch" мы не помещаем get() под узлом verb. Это указывает компилятору, что мы переопределяем существующий глагол, а не определяем новый.
Разница в синтаксисе определения и переопределения служит (помимо прочего) для предотвращения ошибок, которые могут возникнуть в больших проектах. Например, вы можете определить новый verb, который по ошибке имеет то же имя, что и существующий, или попытаться переопределить существующий verb, но неправильно написать его в переопределении. В обоих случаях компилятор выдаст ошибку, предотвращая проблему, которая в противном случае могла бы оставаться незамеченной довольно долгое время.
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!)