easy.dialog.qsp v 1.0

     
     Модуль разрабатывался для плеера QSP v 5.7.0, тестировался только на нём. Не гарантируется правильная работа на плеерах иных версий. Ошибки и недочёты в модуле устраняются по мере нахождения, поэтому гарантировать безупречную работу даже на "классике" не могу. Тестовая загрузка диалога из ста реплик заняла чуть больше 1000 мс. В режиме [NO PRE-SETS] - около 800 мс. Скорость загрузки диалога зависит от объёма диалога и от глубины вложенности реплик.
По всем вопросам и предложениям обращаться:
  • ICQ: 437337904
  • Skype: aleksversus
  • e-mails:
    • aleksversus@mail.ru
    • lex666endless@rambler.ru
    • aleks.versus@yandex.ru
    • aleksversus@yandex.ru
  • Вконтакте: id40090736

Изначально модуль создавался для обеспечения нужд игры "Волшебство и сталь".

Подробное руководство по использованию модуля, с примерами.

Подключение

Предполагается, что вы уже пробовали писать игры на QSP, знаете основные команды и, возможно, знаете, что такое функции, переменные и массивы и как формируется html-форматирование. Впрочем, можно обойтись и без последнего.

Модуль поставляется в комплекте с библиотекой easy.math.qsp и без неё работать не будет.

Чтобы подключить модуль к своей игре, нужно:

  1. Скачать архив и извлечь его содержимое в папку с вашей игрой. В папке с вашей игрой должна появиться папка "lib".
    • После знакомства с примерами файл "game.easy.dialog.qsp" можно удалить.
  2. Проверить в папке "lib" наличие файлов "easy.dialog.qsp" и "easy.math.qsp".
  3. Открыть файл своей игры (например с помощью QGen) и на самой первой локации ввести две строки:
    addqst 'lib/easy.dialog.qsp'
    addqst 'lib/easy.math.qsp'
  4. Сохранить свою игру.

Теперь при запуске вашей игры, модуль "easy.dialog" и библиотека "easy.math" будут подключаться к игре автоматически.

Внимание!!! При подключении библиотек к своей игре будьте внимательны. К игре добавляются только те локации, названия которых отсутствуют в вашей игре. Просмотрите список локаций в подключаемых библиотеках, чтобы предупредить совпадения имён.

Простые диалоги

Чтобы использовать модуль, нужно знать, как написать диалог, чтобы модуль вас понял.

Диалоги пишутся предельно просто и не требуют специальных знаний, кроме тех, которые Вы получите сейчас.

Диалог - это разговор, как правило, двух персонажей. В игре один персонаж - это герой, которым управляет игрок, другой персонаж - неиграбельный, т.е. игрок условно не может им управлять. Персонаж игрока мы будем называть героем, а персонаж, с которым герой вступает в диалог, просто персонажем, актёром, или неписью (от англ. NPC - non-playable character).

Фразы, доступные игроку (и соответственно герою), должны выводиться в виде списка действий, доступных для выбора и выполнения. А фразы актёра должны выводиться непосредственно на экран автоматически. Собственно это все различия между одним типом фраз и другим.

Итак, у нас есть фразы NPC, которые выводятся автоматически, и фразы героя, которые появляются в виде действий и не используются в диалоге, пока игрок не выберет одну из них. Оба типа фраз помещаются между специальными метками. Я называю их тегами.

{: Фраза, которую будет произносить неиграбельный персонаж :} - фигурная скобка, двоеточие, потом идёт реплика персонажа, потом двоеточие и снова фигурная скобка. "{:" - открывающий тег. ":}" - закрывающий тег. Всё, что произносит актёр, помещаем между такими тегами. Каждая отдельная фраза должна помещаться между тегами. Например:

   {: Сегодня прекрасная погода. :}
   {: Не думал я, что будет дождь! :}
   {: На город набегают тучи, осенний сплин кого-то мучит... :}

[: Фраза, которая превратится в действие, а когда игрок выберет действие, станет репликой в диалоге. :] - квадратная скобка, двоеточие, потом идёт реплика героя, снова двоеточие, квадратная скобка. "[:" - открывающий тег, ":]" - закрывающий тег. Всё, что может сказать герой, помещаем между такими тегами. Опять же, каждая отдельная фраза - в отдельных тегах. И каждая такая фраза будет выведена одним действием.

Фразы героя и актёра можно располагать одна в другой, или несколько в одной. Мало того, можно располагать фразы одного типа друг в друге.

Зачем это нужно? А вот зачем: диалог собирается и распознаётся по принципу вопрос-ответ. Допустим, реплика актёра - это вопрос, а реплика героя должна быть ответом на вопрос. Но игроку можно предложить несколько вариантов ответов на вопрос. Чтобы написать такой простой диалог: вопрос и несколько ответов, - делаем так:

Первым делом пишем вопрос. Например, непись должна спросить героя "Как тебя зовут?". Пишем:

      {: Как тебя зовут? :}

А теперь ответы на вопрос "вкладываем" в вопрос:

      {: Как тебя зовут? 
         [:Вася:]
         [:Петя:]
         [:Не твоё дело!:]
         [:Я забыл...:]
      :}

На экран выведется реплика актёра "Как тебя зовут?" и четыре действия. При щелчке на любом из действий, герой будет произносить фразу, написанную в этих репликах. Например, если игрок выберет действие "Я забыл...", на экран выведется фраза героя "Я забыл...".

Как сделать так, чтобы по щелчку на действии произносил фразу не только герой, но и актёр? Всё тем же способом. "Вложить" реплику актёра в реплику героя. Разовьём предыдущий пример:

   {: Как тебя зовут? 
      [:Вася:]
      [:Петя:]
      [:Не твоё дело!:]
      [:А тебя как?
         {:Меня Васькой Пупкиным кличут:}
      :]
   :}

Таким образом, вкладывая реплики друг в друга, вы можете создавать большие ветвистые диалоги. Для примера, вот небольшой ветвистый диалог, на котором отрабатывались первые сборки модуля:

   {:
      Как вас зовут?
      [:
         Меня зовут Вася.
         {:Вас зовут Вася?
            [:Да, меня зовут Вася.
               {:да ну нафиг:}
               [:
               нет. чистая правда
               :]
            :]
            [:Нет, никто меня так не зовёт:]
         :}
         {:
            Это правда, что вас зовут Васей?
            [:Правда
            :]
            [:Неправда:]
         :}
      :]
      [:
         Меня зовут Петя.
         {:
            Вас зовут Петя?
            [:Да{:Как это удобно!:}:]
            [:
               Нет
               {:Вас зовут не Петя? Как неудобно!:}
            :]
         :}
         {:
            Это правда, что вас зовут Петей?
            [:Правда:]
            [:Неправда:]
         :}
         {:Неужели в самом деле Петей Вас зовут?
            [:В самом деле:]
            [:Нет, не в самом:]
         :}
      :]
   :}

Обратите внимание на то, как записаны фразы, которые должны выводиться на экран в диалоге. Перед ними есть пустые строки, символы пробелов и табуляций. Модуль сам очистит фразы от мусора и уберёт пустые строки перед фразой и после неё, а так же пробельные символы перед каждой строкой и после неё. То есть даже если вы вставите фразу между тегами вот так:

   {:







                     Как вас зовут?






   :}

То на экран всё равно будет выведено: "Как вас зовут?" без пробелов перед репликой и пустых строк.

Запуск диалога

Теперь, когда Вы знаете, как составлять диалог, нужно освоить его запуск и уяснить некоторые особенности вывода диалога на экран.

Текст диалога лучше присвоить какой-нибудь переменной. Например:

   $диалог_старушки="{:Добрый вечер, милок.
      [:
         Здравствуйте, бабуся!
      :]
      [:
         Девушка, мы разве знакомы?
      :]
   :}"

Далее делаем вызов диалога командой gosub

gosub 'dialog.int',$диалог_старушки

gosub - это команда обращения к локации. Плеер обращается к локации "dialog.int", выполнет на ней весь код и возвращается обратно. После запятой стоит переменная $диалог_старушки, в которую, как вы помните, мы поместили диалог. Значение этой переменной будет передано локации "dialog.int" для обработки.

При вызове диалога автоматически очистится окно основного описания, окно дополнительного описания и список действий. Когда диалог будет закончен, всё восстановится обратно: и основное и дополнительное описание, и действия даже.

Настройки диалога

Форматирование реплик

по умолчанию, все реплики персонажей выводятся чёрным цветом, все реплики героя серым цветом с курсивом. по умолчанию, перед репликами не ставятся имена, но ставится знак тире. Всё это можно изменить и настроить непосредственно в самом тексте диалога.

Настройки можно вынести за реплики, тогда эти настройки будут считаться настройками по умолчанию. Например:

   npc="
   name:Бабуся:name
   "
   hero="name:Солдат Котёнкин:name"

   {:Добрый вечер, милок.
      [:
         Здравствуйте, бабуся!
      :]
      [:
         Девушка, мы разве знакомы?
      :]
   :}

Настройки доступны самые разнообразные.

npc=" " - настройки отображения фраз неписи в окне основного описания.
hero=" " - настройки отображения фраз героя в окне основного описания.
Настройки, вкладываемые в npc=" " и hero=" " [1] :

name: :name - имя. Если между двоеточиями нет символов, не отображается.
ncolor: - цвет выводимого имени в шестнадцатеричном формате. Если не указан, или указан ошибочно, выводится чёрным (для неписей) или серым (для героя).
nattrib: - аттрибуты выводимого имени, указываются подряд без пробелов:
-b - жирный
-i - курсив
-n - нормальный. Сбрасывает значение прочих атрибутов.
по умолчанию, для героя и для неписи выставлен жирный атрибут, для героя - курсив. Аттрибуты считываются в обратном порядке, начиная с последнего, таким образом происходит и применение. Допустим, мы не хотим, чтобы имя героя отображалось курсивом, но продолжало отображаться жирным шрифтом. Пишем "nattrib:-b-n". Сначала шрифт вернётся к нормальному виду, потом будет применена жирность. Если указать "nattrib:-n-b", то сначала будет применена жирность, а потом шрифт вернётся к нормальному виду.
fcolor: - цвет выводимой фразы в шестнадцатеричном формате. Если не указан, или указан ошибочно, выводится чёрным (для неписей) или серым (для героя).
fattrib: - аттрибуты выводимой фразы (см. список выше). Если не указаны, для фраз героя применяется курсив.
tire: - вид тире. Можно указать любые непробельные символы, и они будут выводиться перед каждой фразой героя или неписи. Чтобы ничего не выводилось, указывается слово "nope". Чтобы выводились символы пробела, используйте   или поместите тег в круглые скобки "(tire: )".

replics=" " - настройки порядка вывода реплик:

repeat: - количество повторов вывода реплик неписи:
one - выводится одна реплика. Например, выбранная случайно.
once - реплики неписи, кроме одиночных, выводятся единожды. Если реплики исчерпаны, выводится последняя.
cicle - реплики выводятся по кругу, т.е. имеют свойство повторяться. по умолчанию.
shuffle: - порядок вывода реплик.
random - случайный порядок
straight - прямой порядок т.е. от первой к последней. по умолчанию.
Счёт порядка вывода реплик и количества повторов ведётся только в течении диалога. Как только диалог будет закрыт, счёт обнуляется. Для сохранения счёта реплик, нужно использовать другие способы.

В записи настроек так же не играет роль количество пробельных символов и переводов строк. Единственное что, между настройками должен находиться хотя бы один пробел, символ табуляции, или символ перевода строки. Примеры настроек:

   npc="
      name:Бабуся:name
      ncolor:ff8800
      fcolor:886600
      fattrib:-i
      tire:nope
      "
   hero="name:Солдат Котёнкин:name ncolor:000000 fcolor:880088 nattrib:-b-n"
   replics="repeat:one
   shuffle:random"

Непосредственно в тексте реплик можно использовать любые html-теги, поддерживаемые плеером, и таким образом дополнительно форматировать текст. Например, вы можете выделить одно слово в реплике другим цветом:

   {:Добрый вечер, милок.
      [:
         Здравствуйте, бабуся!
      :]
      [:
         Девушка, мы разве <font color=#008800>знакомы</font>?
      :]
   :}

Перед выводом на экран любая реплика облекается в точно такие же html-теги в соответствии с настройками. Например, Вы настроили цвет для фразы:

   npc="
      name:Бабуся:name
      fcolor:886600
   "
   {:Добрый вечер, милок.
      [:
         Здравствуйте, бабуся!
      :]
      [:
         Девушка, мы разве <font color=#008800>знакомы</font>?
      :]
   :}

Перед выводом на экран, будет сформирована строка:

<font color=#000000><b>Бабуся:</b></font><font color=#886600> — Добрый вечер, милок.</font>

На экране это будет выглядеть так:

Бабуся: — Добрый вечер, милок.

Модуль автоматически включает распознавание html-тегов в плеере, при обращении к локации "dialog.int".

Когда игрок выберет действие "Девушка, мы разве знакомы?", перед выводом на экран будет сформирована надпись:

<font color=#888888><i> — Девушка, мы разве <font color=#008800>знакомы</font>?</i></font>

На экране это будет выглядеть так:

— Девушка, мы разве знакомы?

То есть будет сохранено автоматическое форматирование и форматирование, которое вы пропишете прямо внутри фразы. Это очень удобно, если нужно форматировать только одну реплику, предложение в реплике, или слово. Html-теги можно использовать и при указании имени, тогда имя будет отличаться от основного цвета.

Наследование настроек

Указывать настройки можно не только для всего диалога вцелом, но и для отдельных реплик и даже для целых веток. Возьмём наш старый пример и немного его модифицируем:

   npc="
   name:Ведущий:name
   ncolor:008888
   fcolor:008888
   "
   hero="
   name:Вася Пупкин:name
   "

   {:
      Как вас зовут?
      [:

      hero="ncolor:008800"
         Меня зовут Вася.
         {:Вас зовут Вася?
            [:
            npc="fcolor:880000"
            Да, меня зовут Вася.
               {:да ну нафиг:}
               [:
               нет. чистая правда
               :]
            :]
            [:Нет, никто меня так не зовёт:]
         :}
         {:
            Это правда, что вас зовут Васей?
            [:Правда
            :]
            [:Неправда:]
         :}
      :]
      [:
      hero="ncolor:ff0000"
         Меня зовут Петя.
         {:
            Вас зовут Петя?
            [:Да{:Как это удобно!:}:]
            [:
               Нет
               {:Вас зовут не Петя? Как неудобно!:}
            :]
         :}
         {:
            Это правда, что вас зовут Петей?
            [:Правда:]
            [:Неправда:]
         :}
         {:Неужели в самом деле Петей Вас зовут?
            [:В самом деле:]
            [:Нет, не в самом:]
         :}
      :]
   :}

До начала диалога мы прописали настройки, которые будут считаться настройками по умолчанию. Первым делом на экран будет выведена фраза "Как вас зовут?" с настройками для npc, которые указаны для всего диалога. Для всего диалога указано так же и имя героя, но не указан ни цвет, ни аттрибуты жирности/наклона, поэтому будут применяться настройки по умолчанию. То есть цвет имени героя будет серым. Однако, как только игрок выберет одно из выпавших действий:

Меня зовут Вася

или

Меня зовут Петя

будут применены настройки, вложенные в это действие. Эти настройки подменят собой настройки, указанные для всего диалога, но лишь те из них, которые указаны явно. Так, если игрок выберет действие "Меня зовут Вася", имя героя для текущей и всех вложенных реплик будет зелёным. Все остальные настройки останутся прежними. Смотрим дальше. После выбора действия "Меня зовут Вася", актёр "переспросит": "Вас зовут Вася?", фраза эта появится на экране всё с теми же общими настройками для всего диалога. Однако вслед за ней на экране появятся два действия:

Да, меня зовут Вася.

и

Нет, никто меня так не зовёт

При выборе первого, настройка цвета для вывода фраз неписи изменится, и следующая фраза "да ну нафиг" появится на экране, раскрашенная красным цветом. Таким образом, как уже было сказано, для ветки реплик, или реплики, изменяется только та настройка, которая была явно указана. Не указанные настройки достаются в наследство от старшей реплики (т.е. той, в которую мы вложили текущую).

Сброс настроек

Для сброса настроек в значение, предопределённое для всего диалога (значения по умолчанию), используется ключевое слово default.

npc="default" - сбрасывает все настройки реплик неписи в значения по умолчанию (дефолтные значения),
hero="default" - сбрасывает все настройки реплик героя в дефолтные значения,
replics="default" - сбрасывает все настройки порядка вывода реплик в дефолтные значения.

Можно сбрасывать так же и отдельную настройку, указав слово default после двоеточия. Например, сбросить цвет фраз:

fcolor:default

Порядок вывода реплик

Выше уже были обозначены основные настройки порядка вывода реплик. Рассмотрим подробнее их поведение на примере последнего диалога, а точнее вот этой его части:

  [:
  hero="ncolor:ff0000"
     Меня зовут Петя.
     {:
        Вас зовут Петя?
        [:Да{:Как это удобно!:}:]
        [:
           Нет
           {:Вас зовут не Петя? Как неудобно!:}
        :]
     :}
     {:
        Это правда, что вас зовут Петей?
        [:Правда:]
        [:Неправда:]
     :}
     {:Неужели в самом деле Петей Вас зовут?
        [:В самом деле:]
        [:Нет, не в самом:]
     :}
  :]

Для наглядности разные реплики выделены разными цветами.

Предположим игрок выбрал действие "Меня зовут Петя". Какая из трёх реплик неписи должна быть выведена? Вот за это и отвечают настройки порядка вывода реплик.

shuffle: - настройка, определяющая выбор следующей реплики. Можно указать один из вариантов:

random - реплика неписи будет выбрана случайным образом.
straight - будет выбрана следующая реплика в порядке очереди. Т.е. если это первый выбор действия "Меня зовут Петя", будет выведена первая реплика (отмечена красным цветом), если игрок второй раз нажмёт на действие, будет выведена вторая реплика (отмечена фиолетовым цветом), и т.д.

repeat: - настройка, определяющая режим повтора реплик. Можно указать один из вариантов:

one - всякий раз при выборе этого действия будет выводиться одна и та же реплика. Например, была выставлена настройка shuffle:random. Когда игрок выберет действие "Меня зовут Петя", случайным образом будет выбрана одна из реплик, и показана на экране. В следующий раз, когда игрок вновь выберет это же действие, вновь на экран будет выведена та же самая реплика.

once - реплики будут выводиться не повторяясь по одному разу. После вывода последней реплики всё время будет выводиться только она. Например, была выставлена настройка shuffle:straight. Когда игрок выберет действие "Меня зовут Петя" первый раз, на экран будет выведена первая реплика (выделена красным цветом. Во второй раз появится вторая реплика (выделена фиолетовым), в третий раз - третья (синяя. Последняя реплика). В четвёртый, пятый и далее будет выводиться третья реплика.

cicle - реплики будут выводиться непрерывно по кругу. Если выставлена настройка shuffle:straight, первый раз выведется первая реплика, второй раз - вторая, третий - третья, на четвёртый раз - вновь первая, потом снова: вторая, третья, первая, вторая... и т.д. Для настройки shuffle:random выбор реплики несколько сложнее. Первый раз, когда игрок выберет действия, реплика определится случайным образом из трёх. На второй раз случайным образом выбирается одна из двух оставшихся реплик. Потом последняя оставшаяся. В четвёртый раз вновь происходит выбор случайным образом из трёх реплик, и т.д.

Чуть более сложные диалоги

Корни, ветви и узлы

Для продолжения нам нужно разобраться в структуре диалогов и договориться о терминологии.

Из предыдущего раздела мы узнали, как пишутся диалоги. Реплики персонажа помещаются между тегами "{:" и ":}", а реплики героя между "[:" и ":]". При этом реплики персонажа должны выводиться на экран автоматически, а реплики героя сначала представать в виде действий. Чтобы соотнести одни реплики с другими, мы научились "вкладывать" одни реплики в другие. Выбор одной из реплик приводит к выводу вложенных реплик, те в свою очередь тянут за собой ещё вложенные реплики и т.д.

Но с чего же модуль начинает? Должна же быть какая-то реплика, с которой начинается вывод всех остальных реплик. Да. Такая реплика есть, хотя до сих пор мы не называли её репликой и не использовали как реплику. Это реплика, которая не включается в теги и собственно вынесена из диалога. В ней прописываются все дефолтные настройки. Она называется корнем диалога, корневой репликой или заголовком диалога. Модуль не применяет к ней никаких настроек форматирования и все прописанные в ней фразы выводятся как есть, без имени персонажа или героя перед ними. Возьмём предпоследний пример и слегка его изменим, чтобы взглянуть на то, как выглядит корень диалога (подсвечен фиолетовым цветом):

   npc="
   name:Ведущий:name
   ncolor:008888
   fcolor:008888
   "
   hero="
   name:Вася Пупкин:name
   "
   Это заголовок диалога. Его корень. Данные реплики будут выведены
   на экран как есть, без применения форматирования из настроек диалога.
   Тем не менее символы пробелов и табуляций перед строками, а так же
   лишние переводы строк будут удалены.
   {:
      Как вас зовут?
      [:

      hero="ncolor:008800"
         Меня зовут Вася.
         {:Вас зовут Вася?
            [:
            npc="fcolor:880000"
            Да, меня зовут Вася.
               {:да ну нафиг:}
               [:
               нет. чистая правда
               :]
            :]
            [:Нет, никто меня так не зовёт:]
         :}
         {:
            Это правда, что вас зовут Васей?
            [:Правда
            :]
            [:Неправда:]
         :}
      :]
      [:
      hero="ncolor:ff0000"
         Меня зовут Петя.
         {:
            Вас зовут Петя?
            [:Да{:Как это удобно!:}:]
            [:
               Нет
               {:Вас зовут не Петя? Как неудобно!:}
            :]
         :}
         {:
            Это правда, что вас зовут Петей?
            [:Правда:]
            [:Неправда:]
         :}
         {:Неужели в самом деле Петей Вас зовут?
            [:В самом деле:]
            [:Нет, не в самом:]
         :}
      :]
   :}

Итак, корень - это реплика, которая помещена вне остального диалога, вне тегов, но меж тем в неё "помещены" все остальные реплики. Корень - это реплика, с которой начинается вывод диалога на экран. Естественно, если текст реплики отсутствует, на экран ничего не выводится, даже пустые строки.

Когда модуль находит первую реплику диалога (его корень) и выводит её на экран (или не выводит, если фраза не прописана), он находит также и все вложенные реплики. В нашем примере он отыщет всего одну реплику "Как вас зовут?". Для наглядности я выделил её красным:

   npc="
   name:Ведущий:name
   ncolor:008888
   fcolor:008888
   "
   hero="
   name:Вася Пупкин:name
   "
   Это заголовок диалога. Его корень. Данные реплики будут выведены
   на экран как есть, без применения форматирования из настроек диалога.
   Тем не менее символы пробелов и табуляций перед строками, а так же
   лишние переводы строк будут удалены.
   {:
      Как вас зовут?
      [:

      hero="ncolor:008800"
         Меня зовут Вася.
         {:Вас зовут Вася?
            [:
            npc="fcolor:880000"
            Да, меня зовут Вася.
               {:да ну нафиг:}
               [:
               нет. чистая правда
               :]
            :]
            [:Нет, никто меня так не зовёт:]
         :}
         {:
            Это правда, что вас зовут Васей?
            [:Правда
            :]
            [:Неправда:]
         :}
      :]
      [:
      hero="ncolor:ff0000"
         Меня зовут Петя.
         {:
            Вас зовут Петя?
            [:Да{:Как это удобно!:}:]
            [:
               Нет
               {:Вас зовут не Петя? Как неудобно!:}
            :]
         :}
         {:
            Это правда, что вас зовут Петей?
            [:Правда:]
            [:Неправда:]
         :}
         {:Неужели в самом деле Петей Вас зовут?
            [:В самом деле:]
            [:Нет, не в самом:]
         :}
      :]
   :}

Распознав тип этой реплики, модуль поймёт, что она должна быть выведена автоматически и повторит ту же процедуру, что проделал с корневой репликой, но на этот раз применит к ней настройки. Т.е. выведет фразу как реплику неписи. Затем он вновь проверит, нет ли в этой реплике вложенных и обнаружит целых две. Каждую из них я выделил отдельным цветом:

   npc="
   name:Ведущий:name
   ncolor:008888
   fcolor:008888
   "
   hero="
   name:Вася Пупкин:name
   "
   Это заголовок диалога. Его корень. Данные реплики будут выведены
   на экран как есть, без применения форматирования из настроек диалога.
   Тем не менее символы пробелов и табуляций перед строками, а так же
   лишние переводы строк будут удалены.
   {:
      Как вас зовут?
      [:hero="ncolor:008800"
         Меня зовут Вася.
         {:Вас зовут Вася?
            [:
            npc="fcolor:880000"
            Да, меня зовут Вася.
               {:да ну нафиг:}
               [:нет. чистая правда:]
            :]
            [:Нет, никто меня так не зовёт:]
         :}
         {:
            Это правда, что вас зовут Васей?
            [:Правда
            :]
            [:Неправда:]
         :}
      :]
      [:hero="ncolor:ff0000"
         Меня зовут Петя.
         {:
            Вас зовут Петя?
            [:Да{:Как это удобно!:}:]
            [:Нет
               {:Вас зовут не Петя? Как неудобно!:}
            :]
         :}
         {:
            Это правда, что вас зовут Петей?
            [:Правда:]
            [:Неправда:]
         :}
         {:Неужели в самом деле Петей Вас зовут?
            [:В самом деле:]
            [:Нет, не в самом:]
         :}
      :]
   :}

Каждая из этих реплик появится в виде действия. Искать вложенные в них реплики не имеет смысла, пока игрок не выберет одно из действий, поэтому модуль пока остановится.

Мы тоже остановимся, но совсем по другой причине. На этом примере мы увидели сразу два элемента диалога: узел и ветви. Узлом считается любая реплика, в которую "вложены" другие реплики. Не обязательно две. Может быть и одна, и три, и сто тысяч миллионов. Узлы различаются по типам точно так же, как и реплики: узел с репликой персонажа (актёрский узел) и узел с репликой героя (геройский узел, узел действия). Реплика, в которую не "вложено" ни одной другой реплики, называется финальной (последней, конечной. В нашем примере финальные реплики выделены чёрным цветом.). Две однотипные реплики, вложенные в одну реплику, считаются (и называются) ветвями диалога. Такие реплики называются одноуровневыми, то есть находящимися на одном уровне. Уровень реплики считается по количеству узлов, которые ей предшествовали. Поскольку корневой реплике не предшествует ни одного узла, она считается нулевым уровнем. Реплика "Как вас зовут?" из примера - первый уровень, и т.д.

Скрытый узел

Иногда нужно скрыть узел или целую ветку, чтобы она не выводилась ни при каких условиях, пока не будет осуществлён принудительный переход. Чтобы сделать это нужно завести пустой узел действия. Особенность работы модуля состоит в том, что он не выводит действия с пустым именем, поэтому в пустых узлах действий можно прятать другие реплики и целые ветви. Пример:

   [:
      {: Скрытая реплика :}
      {: Ещё одна скрытая реплика :}
      {: А это целая скрытая ветка
         [:
            Да, а вот и ответвление
            {:И финальная реплика:}
         :]
         [:
            Да, а вот и другое ответвление
            {:И ещё одна финальная:}
         :]
      :}
   :]

Ремарки

Ремарка - это авторский текст, который бывает нужно поместить между репликами. Специальных настроек для ремарок нет, но делать их, тем не менее, просто. Для этого в текущую реплику неписи или героя можно добавить реплику с настройками, отменяющими имя актёра и тире (код ремарки выделен голубым цветом):

   {:
      npc="name:Ведущий:name"
      Как вас зовут?
      {:   
         npc="name::name
         tire:nope"
         <i>
         Что Вы на это ответите?
         </i>
      :}
   :}

Ремарки желательно делать финальными репликами, хотя и не обязательно.

Ремарки внутри реплики

Ремарки внутри реплики - это некоторые авторские пояснения, которые нужно вывести непосредственно внутри реплики. Например, прервать речь актёра, чтобы пояснить, какое действие он выполняет, пока произносит фразу. Или указать с какой интонацией или силой произносится фраза. Вот примеры таких ремарок:

Мишель (громко): — Яичница с ветчиной, это сколько?

Мыса: — На весну оставляю, — хныча, малец потёр багровое ухо. — Голодно будет.

В первом случае ремарка идёт сразу после имени и её отличие от имени в том, что она написана нежирным шрифтом, курсивом и серым цветом. Во втором случае ремарка прерывает прямую речь, и в отличие от прямой речи она написана курсивом и другим цветом. Последнюю сделать проще всего. Мы просто вставляем в фразу html-теги, которые изменят цвет и наклон текста:

   {:
      npc="name:Мыса:name ncolor:008800 fcolor:000000 nattrib:-b-n fattrib:-n"
      На весну оставляю, <i><font color="888888">— хныча, малец потёр багровое ухо. —</font></i> Голодно будет.
   :}

Чтобы сделать ремарку для первого случая, можно прибегнуть к одной из хитростей. Например, можно задать персонажу пустое имя и полностью прописать строку реплики со всеми именами, тегами и пр.:

   {:
      npc="name:Мишель:name ncolor:008800 fcolor:000000 nattrib:-b-n fattrib:-n"
      {:npc="name::name"
      <font color="008888"><b>Мишель </b><i><font color="888888">(громко)</font></i><b>:</b></font>
      <font color="000000"> — Яичница с ветчиной, это сколько?</font>
      :}
   :}

Или изменить имя персонажа на имя, в которое уже будет включена ремарка:

   {:
      npc="name:Мишель:name ncolor:008800 fcolor:000000 nattrib:-b-n fattrib:-n"
      {:npc="name:Мишель </b><i><font color="888888">(громко)</font></i><b>:name"
      — Яичница с ветчиной, это сколько?
      :}
   :}

В последнем случае писанины меньше, однако нужно очень хорошо представлять себе, какие настройки применяются к имени.

Как избежать очистки списка действий

Особенность поведения модуля такова, что если узлом была реплика актёра, а ветвями являются реплики героя, производится очистка списка действий и вывод новых. Чтобы вывести новое действие при щелчке на одном из действий списка, но не удалять старые, помещать реплику героя следует непосредственно в предыдущую реплику героя. Так сделано в предпоследнем примере в реплике "Да, меня зовут Вася.":

   [:   npc="fcolor:880000"
      Да, меня зовут Вася.

      {:да ну нафиг:}
      [:нет. чистая правда:]
   :]

Удаление действия из списка

Иногда необходимо не добавлять действия, не удаляя прочие, а удалить одно действие из списка не обновляя список. Для этого была введена команда selectact.delete. Прописывать её можно только для реплик героя.

Блок последовательных реплик

Иногда нужно вывести сразу не одну, не две, а несколько реплик поочерёдно от героя и персонажа

Для примера возьмём вот такой отрывок:

Мишель (громко): Яичница с ветчиной, это сколько?
Женский голос (громко): Сто восемьдесят.
Мишель поворачивается лицом к стойке.
Мишель: Ладно. Делайте.
Женщина: Хорошо.

У нас два собеседника: Женщина и Мишель. Пусть неписью побудет Мишель, а героем - Женщина.

Как обычно, для реплики мы задаём настройки:

   npc="name:Мишель:name ncolor:008888 nattrib:-b-n fcolor:000000 fattrib:-n tire:nope"
   hero="name:Женщина:name ncolor:888888 nattrib:-b-n fcolor:000000 fattrib:-n tire:nope"

Если бы мы использовали только возможность вкладывать одну реплику в другую, мы могли бы написать этот диалог так:

   npc="name:Мишель:name ncolor:008888 nattrib:-b-n fcolor:000000 fattrib:-n tire:nope"
   hero="name:Женщина:name ncolor:888888 nattrib:-b-n fcolor:000000 fattrib:-n tire:nope"
   {:npc="name::name"
   <font color="008888"><b>Мишель</b> (громко)<b>:</b></font> <font color="000000">Яичница с ветчиной, это сколько?</font>
      {:
      <font color="888888"><b>Женский голос</b> (громко)<b>:</b></font> <font color="000000">Сто восемьдесят.</font>
         {:
         <i>Мишель поворачивается лицом к стойке.</i>
            {:npc="name:Мишель:name"
               Ладно. Делайте.
               {:npc="name:Женщина:name ncolor:888888 fcolor:000000"
                  Хорошо.
               :}
            :}
         :}
      :}
   :}

Но это не только громоздко, но ещё и неудобно, и даёт дополнительную нагрузку на плеер. Специально для таких случаев была придумана конструкция блоков реплик (блок фраз). Делается она очень просто. Между тегами frase-block: :frase-block помещаются строки нашего диалога. Каждая строка считается отдельной фразой/репликой. Если в начале строки вписать <npc-name>, то к строке будет применено форматирование согласно настройкам неписи. Если в начале строки указать <hero-name>, то к ней будет применено форматирование согласно настройками героя. Если ни той ни другой метки в строке не обнаружено, дополнительное форматирование к строке не применяется. Таким образом наш диалог можно записать в одну "реплику":

   npc="name:Мишель:name ncolor:008888 nattrib:-b-n fcolor:000000 fattrib:-n tire:nope"
   hero="name:Женщина:name ncolor:888888 nattrib:-b-n fcolor:000000 fattrib:-n tire:nope"
   {:
      frase-block:
         <font color="008888"><b>Мишель</b> (громко)<b>:</b></font> <font color="000000">Яичница с ветчиной, это сколько?</font>
         <font color="888888"><b>Женский голос</b> (громко)<b>:</b></font> <font color="000000">Сто восемьдесят.</font>
         <i>Мишель поворачивается лицом к стойке.</i>
         <npc-name>Ладно. Делайте.
         <hero-name>Хорошо.
      :frase-block
   :}

Фразы, указанные в блоке, будут выводиться одна за другой с небольшой задержкой. Блок фраз имеет приоритет перед простым текстом реплики.

Реплики по условию

Иногда нам необходимо намеренно скрыть реплику до тех пор, пока не будет выполнено определённое условие. Например, реплика должна появляться только в тех случаях, когда в рюкзаке героя есть "Странный синий камушек", а до той поры быть скрыта. Чтобы написать такую скрытую реплику, используем специальные теги iffing: :iffing. Если Вы не первый день пользуетесь QSP, Вы знаете, что такое оператор условия и уже видели конструкции типа:


   if obj('Странный синий камушек'):
      ! ---- какой-то код .... ---------
   end

Так вот. Между тегами iffing: :iffing нужно поместить всё, что находилось бы в этой конструкции между "if" и ":". То есть реплика по условию выглядела бы так:

   {:
      iffing:obj('Странный синий камушек'):iffing
      О, я вижу ты нашёл странный синий камушек!
   :}

Или так:

   [:
      iffing:obj('Странный синий камушек'):iffing
      Не знаешь, что это за странный синий камушек?
   :]

Используя подобную возможность, следует помнить, что все реплики, "вложенные" в реплику, выводимую по условию, окажутся недоступны до выполнения условия.

Выполнение кода при выводе реплики

Иногда при выводе реплики нужно выполнить какой-нибудь код. Особенно часто это бывает необходимо при выборе действия (выводится реплика героя). На такой случай предусмотрены специальные теги dynamic-code: :dynamic-code. Между ними можно разместить любой код QSP, который будет выполнен только при выводе реплики на экран. Если текст реплики не задан, на экран, конечно, ничего не выведется, но код всё равно будет выполнен. Код выполняется непосредственно перед выводом реплики на экран, поэтому если Вы пропишете вывод строки из кода, строка появится на экране до выводимой реплики. Вот пример вывода ремарки до реплики, используя код в реплике.

   {:
      dynamic-code:
      *pl '<i><font color="888888">Мишель поворачивается лицом к стойке.</font></i>'
      :dynamic-code:
      Ладно. Делайте.
   :}

Принудительные переходы по репликам. Закрытие диалога

Принудительные переходы по репликам можно вставлять на любой реплике. Такие переходы не зависят от выполнения условий вывода реплики, можно перейти даже на скрытые реплики. Однако не рекомендуется злоупотреблять переходами, иначе можно создать непрямое зацикливание. Принудительные переходы необходимы, если Вам нужно переключиться на другую ветку диалога, или вернуться на пару узлов обратно. Можно выполнить переходы сразу на несколько реплик одновременно, однако не стоит забывать об очистке списка действий и порядке его осуществления.

Для переходов по репликам используются следующие команды:

levelup:## — переход по узлам "назад". Вместо "решёток" указывается на сколько узлов нужно вернуться по репликам. Тип реплик при этом не учитывается. К примеру:

   replics="repeat:cicle
   shuffle:random" 
   npc="name:Ведущий:name
   ncolor:008888
   fcolor:008888"
   hero="name:Вася пупкин:name"
   {:marker:firstqst
   Как вас зовут?
      [:Меня зовут Вася.
         {:
            Вас зовут Вася?
            [:Да, меня зовут Вася.:]
            [:Нет, никто меня так не зовёт
            levelup:5:]
         :}
         {:
            Это правда, что вас зовут Васей?
            [:Правда:]
            [:Неправда
            levelup:3:]
         :}
      :]
      [:
         Меня зовут Петя.
         {:
            Вас зовут Петя?
            [:Да{:Как это удобно!:}:]
            [:Нет
               levelup:3
               {:Вас зовут не Петя? Как неудобно!:}
            :]
         :}
         {:
            Это правда, что вас зовут Петей?
            [:Правда:]
            [:Неправда
            leveljump:firstqst:]
         :}
         {:Неужели в самом деле Петей Вас зовут?
            [:В самом деле
            closeup:]
            [:Нет, не в самом
            levelup:3:]
         :}
      :]
   :}

Здесь можно видеть несколько переходов по узлам, они выделены тёмно-голубым цветом. Сначала рассмотрим два из них.

Реплика "Неправда" (подсвечена красным цветом) должна осуществлять переход на три узла назад. Она вложена в реплику "Это правда, что вас зовут Васей?", которую произнёс актёр. То есть это один уровень назад. Реплика "Это правда, что вас зовут Васей?" вложена в реплику "Меня зовут Вася", которую произнёс герой. Это два уровня назад. Эта реплика, в свою очередь, вложена в реплику неписи "Как вас зовут?". Она и будет репликой, на которую осуществится принудительный переход - три узла назад.

Реплика "Нет, никто меня так не зовёт" (подсвечена синим цветом) должна осуществлять переход на пять узлов назад. Однако это невозможно, поскольку ей предшествовало всего четыре реплики, включая корневую. Переместиться на корневую реплику нельзя, поэтому будет осуществлено перемещение на реплику первого уровня "Как вас Зовут?".

leveljump:MARKER — переход минуя все условия и промежуточные узлы на реплику с указанной меткой.

Вместо "MARKER" указывается метка. Чтобы поставить метку на реплике, нужно использовать одиночный тег marker:, после которого следует прописать без пробелов любое сочетание символов (можно использовать только буквы и цифры и символ подчёркивания). Эти символы и будут меткой. Пример реплики, осуществляющей переход на метку, выделен фиолетовым цветом в предыдущем примере. Сама метка выделена жёлтым цветом.

Если указанная в leveljump: метка отсутствует, переход не осуществится.

Чтобы осуществить переход сразу на несколько реплик, нужно указать для каждой из этих реплик одинаковые метки, а потом для реплики, с которой нужно перейти, указать команду leveljump: и эту метку. Либо выставить для каждой реплики свою собственную метку, а потом прописать в реплике, с которой нужно перейти, команду leveljump: столько раз на сколько реплик нужно перейти, с указанием каждой выставленной метки. Разница между двумя этими способами в том, что во втором случае Вы можете контролировать порядок вызова реплик. Вот оба варианта для сравнения:

replics="repeat:cicle
shuffle:random" 
npc="name:Ведущий:name
ncolor:008888
fcolor:008888"
hero="name:Вася пупкин:name"
{:marker:firstqst
Как вас зовут?
   [:
      Меня зовут Петя.
      {:
         Вас зовут Петя?
         [:Да{:Как это удобно!:}:]
         [:Нет
            levelup:3
            {:Вас зовут не Петя? Как неудобно!:}
         :]
      :}
      {:
         Это правда, что вас зовут Петей?
         [:Правда:]
         [:Неправда
         leveljump:firstqst:]
      :}
      {:Неужели в самом деле Петей Вас зовут?
        [:В самом деле
           closeup
        :]
        [:Нет, не в самом
           levelup:3
        :]
      :}
   :]
   [:
      {:А здесь у нас скрытая реплика
         marker:firstqst
      :}
   :]
:}
replics="repeat:cicle
shuffle:random" 
npc="name:Ведущий:name
ncolor:008888
fcolor:008888"
hero="name:Вася пупкин:name"
{:
   marker:firstqst
   Как вас зовут?
   [:
      Меня зовут Петя.
      {:
         Вас зовут Петя?
         [:Да{:Как это удобно!:}:]
         [:Нет
            levelup:3
            {:Вас зовут не Петя? Как неудобно!:}
         :]
      :}
      {:
         Это правда, что вас зовут Петей?
         [:Правда:]
         [:Неправда
         leveljump:firstqst
         leveljump:hidereplics
         :]
      :}
      {:Неужели в самом деле Петей Вас зовут?
         [:В самом деле
         closeup:]
         [:Нет, не в самом
         levelup:3:]
      :}
   :]
   [:
      {:А здесь у нас скрытая реплика
         marker:hidereplics
      :}
   :]
:}

В первом случае модуль сам отыщет первую реплику, которую выведет. Скорей всего это будет реплика с наивысшим уровнем. Во втором случае сначала будет осуществлён переход на реплику с меткой firstqst, а затем на реплику с меткой hidereplics. Т.е. именно в том порядке, в котором записаны команды leveljump: в реплике "Неправда", выделенной фиолетовым цветом.

Чтобы принудительно закрыть диалог из реплики, нужно ввести команду closeup. В последних примерах реплика "В самом деле" будет закрывать диалог. При закрытии диалога восстановится описание и действия локации, с которой диалог был вызван.

Названия действий

по умолчанию, в качестве названия действия выступает полный текст реплики. Это не всегда удобно. Иногда нужно вывести очень длинную реплику, полный текст которой будет смотреться неуместно в названии действия. На такой случай в модуле предусмотрена конструкция act-name: :act-name. Текст, помещённый между этими двумя тегами, будет использоваться в качестве названия действия. Пример:

   {:
      Как вас зовут?
      [:
      act-name:Вася:act-name
      Вообще моё имя - Василий. Но друзья зовут меня - Вася...
      :]
   :}

В этом диалоге на экран сначала выведется реплика "Как вас зовут?" и действие с названием "Вася". При щелчке по действию, на экране появится реплика "Вообще моё имя - Василий. Но друзья зовут меня - Вася...".

Всё довольно просто. Когда нам нужно вывести название действия, отличное от произносимой реплики, используем эту конструкцию.

Эту же конструкцию следует использовать, если в узле действия Вы использовали конструкцию вывода блока фраз frase-block: :frase-block. Однако здесь можно написать и обыкновенную реплику без тегов act-name: :act-name, и она выведется в названии действия. Чтобы было понятнее, пример, когда в качестве названия действия выводится реплика:

   {:
      Как вас зовут?
      [:
      Меня зовут Вася.
      :]
      [:
      Я стесняюсь...
      frase-block:
         <hero-name>Мне так неловко...
         <npc-name>Отчего же?
         <hero-name>Моё имя так необычно звучит...
         <npc-name>Ну же, смелее!
         <hero-name>Вартасахан Бздюльгахаев!
         <hero-name>Можно просто: Вася.
      :frase-block
      :]
   :}

Пример, когда в качестве названия действия, будет выведен текст, помещённый между act-name: :act-name:

   {:
      Как вас зовут?
      [:
      act-name:Вася:act-name
      Меня зовут Вася.
      :]
      [:
      act-name:Мне неловко...:act-name
      Я стесняюсь...
      frase-block:
         <hero-name>Мне неловко...
         <npc-name>Отчего же?
         <hero-name>Моё имя так необычно звучит...
         <npc-name>Ну же, смелее!
         <hero-name>Вартасахан Бздюльгахаев!
         <hero-name>Можно просто: Вася.
      :frase-block
      :]
   :}

Пример, когда название у действия будет, но реплику герой не произнесёт:

   {:
      Как вас зовут?
      [:
      act-name:Вася:act-name
         {:npc="name::name"
         
         Он бы мог сказать, что его зовут Вася. Но на самом деле его звали Вартасахан.
         
         :}
      :]
   :}

И напоследок. Блок фраз, который не появится в виде действия, потому что неоткуда извлечь название действия:

   {:
      Как вас зовут?
      [:
      frase-block:
         <hero-name>Мне неловко...
         <npc-name>Отчего же?
         <hero-name>Моё имя так необычно звучит...
         <npc-name>Ну же, смелее!
         <hero-name>Вартасахан Бздюльгахаев!
         <hero-name>Можно просто: Вася.
      :frase-block
      :]
   :}

Комментирование реплик

Все реплики можно комментировать. Комментарии записываются так же, как записываются в обычном html-документе, т.е. внутри конструкции <!-- -->. Перед выводом реплик все подобные конструкции будут удалены. Комментарии главным образом предназначены для помещения в них настроек и прочих текстов, которые не должны входить в реплику. Конечно, настройки можно указывать и не в комментариях, но если разместить их внутри комментария, обработка каждой реплики сократится на несколько миллисекунд. При больших уровнях вложенности это может существенно сократить время вывода всех реплик на экран. Все конструкции, расширяющие реплику, такие как frase-block: :frase-block, act-name: :act-name, iffing: :iffing, и др., можно и нужно размещать в комментариях. Лучше всего всё это уместить в одном комментарии.

Запрещено вкладывать комментарии в конструкции iffing: :iffing и dynamic-code: :dynamic-code, а так же вкладывать комментарии в комментарии. Вот пример неправильного размещения комментариев (подсвечены красным цветом):

   <!-- npc="
   name:Ведущий:name
   ncolor:008888<!-- цвет имени неписи -->
   fcolor:008888
   "
   hero="
   name:Вася Пупкин:name
   "
   -->
   {:
      Как вас зовут?
      [:

      hero="ncolor:008800"
         Меня зовут Вася.
         {:
         iffing:a=0<!-- реплика выводится, если "a" равно нулю -->:iffing
         dynamic-code:a=1:dynamic-code<!-- изменяем значение "a", чтобы реплика больше не выводилась -->
         Вас зовут Вася?
            [:
            npc="fcolor:880000"
            Да, меня зовут Вася.
               {:да ну нафиг:}
               [:
               нет. чистая правда
               :]
            :]
            [:Нет, никто меня так не зовёт:]
         :}
         {:
         iffing:a>0:iffing<!-- реплика выводится, если "a" больше нуля -->
         dynamic-code:if a<5: a+=1<!-- изменяем значение "a", чтобы реплика больше не выводилась -->:dynamic-code
            Это правда, что вас зовут Васей?
            [:Правда
            :]
            [:Неправда:]
         :}
      :]
   :}

Сами реплики в комментарии не помещаются.

Если комментарий разместить, например, в блоке фраз, то он будет проигнорирован html-рендерером, поскольку написан по правилам комментариев в html.

Заключение

     Если Вы прочитали данное руководство залпом и составление диалогов для интерпретации модулем easy.dialog показалось Вам слишком сложным, ознакомьтесь с другими возможностями составления диалогов в QSP. Чуть ниже даны ссылки на примеры от WladySpb и y4ndexx, а так же на пример реализации сложного диалога с помощью движка от Olegus.t.Gl. Может быть Вам подойдёт что-нибудь из этого. Или перечитайте руководство ещё раз, и ознакомьтесь с примерами диалогов, составленных для модуля, в файле game.easy.dialog.qsp, чтобы лучше усвоить как и что работает. Так же вы можете обратиться к составленному для easy.dialog краткому описанию, где в общих чертах раскрываются термины, понятия, структуры и принципы конструирования диалогов.

     В тексте руководства использованы реплики из киносценариев и пьес разных авторов.

     Огромное спасибо WladySpb и y4ndexx, подготовившим примеры диалогов для QSP, добавленные в официальные материалы для разработчиков. Они мне очень помогли, когда я только познакомился с QSP. А так же - Olegus t.Gl за реализацию "диалога Чешира", откуда я почерпнул немало идей и решений. Диалоги, реализованные в примерах, а так же "диалог Чешира", я постарался воспроизвести в примере использования модуля. Смотри файл "game.easy.dialog.qsp" в архиве.

Содержание

  1. Подключение
  2. Простые диалоги
  3. Запуск диалога
  4. Настройки диалога
    1. Форматирование реплик
    2. Наследование настроек
    3. Сброс настроек
    4. Порядок вывода реплик
  5. Чуть более сложные диалоги
    1. Корни, ветви и узлы
    2. Скрытый узел
    3. Ремарки
      • Ремарки внутри реплики
    4. Как избежать очистки списка действий
      • Удаление действия из списка
    5. Блок последовательных реплик
    6. Реплики по условию
    7. Выполнение кода при выводе реплики
    8. Принудительные переходы по репликам. Закрытие диалога
    9. Названия действий
    10. Комментирование реплик
  6. Заключение

Примечания:

[1] Возможно так же написание с использованием одиночного апострофа: npc=' ' hero=' ' replics=' '




 Aleks Versus'easy.libraries'2014
Aleks Versus'GAM'RUS'2014