Skip to content

AleksVersus/easy.dialog

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Easy.Dialog

Версия 3.x.x

Разрабатывался для плееров версии 5.8.0 и выше, а в частности плеера qSpider. Тестировался на qSpider и на классическом плеере версии 5.8.0.

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

Вторая версия так и не была написана, поэтому не ищите промежуточный вариант.

В этой версии easy.dialog было решено отделить самую ресурсоёмкую часть, и вынести её за пределы QSP. А именно: генератор диалогов был переписан на язык python. Теперь процесс написания и воспроизведения диалогов в модуле состоит из трёх этапов:

  1. Пишем диалоги, используя специальный синтаксис "edsynt".
  2. Используем генератор, чтобы сконвертировать диалоги, написанные в "edsynt", в рабочий код QSP и добавляем их к своей игре.
  3. С помощью интерпретатора диалогов, написанного на QSP, воспроизводим диалоги в игре.

Общие правила написания исходников для диалогов не изменились. Изменились названия некоторых настроек и команд, а также вместо отдельных настроек различных элементов для оформления реплики появились "роли" — объекты, в которых автор по своему желанию может прописывать любое оформление репликам.

TODO: ниже по тексту будут попадаться вот такие вставочки. Это записи для меня. Я не стал их комментировать, т.к. в исходниках всё равно видно. Ориентируясь на эти вставочки, вдальнейшем я буду модернизровать модуль

Требования

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

Для написания диалогов требуется, чтобы у вас на компьютере был установлен интерпретатор python версии 3.11 или выше, так как именно под эту версию писался генератор диалогов.

TODO: Пока что требуется редактировать непосредственно файл easy.dialog.generator.py для того, чтобы генератор работал с вашими файлами диалогов. В будущем можно будет разместить рядом с генератором файл json и список диалогов будет подхватываться из него. (При добавлении не забыть поправить инструкцию в разделе Работа с генератором)

Интерпретатор "easy.dialog" в большей степени предназначен для использования в qSpider, хотя вы можете применять его и в классическом QSP, с учётом урезанного HTML.

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

Модуль поставляется в комплекте с библиотеками "easy.math" и "easy.database" и без них работать не будет.

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

Внимание!!! Если в вашей игре небольшие диалоги, или вы пишете только один диалог на всю игру, сравните объёмы текста и объём модуля диалогов (в сумме с модулями, которые идут в комплекте). Возможно целесообразнее написать вашу игру без использования easy.dialog.

Установка и подключение

  1. Скачайте и распакуйте в папку с вашей игрой архив из последнего релиза. В ней должны появиться три папки и два файла:
    • папка tools — руководство, генератор, примеры edsynt, и пакет с подсветкой синтаксиса для Sublime Text.
    • папка lib — модуль "easy.dialog" и вспомогательные библиотеки.
    • папка res — ресурсы (ассеты) игры для qSpider.
    • файл eid.start_game. — пример игры, в которой используется модуль "easy.dialog".
    • файл game.cfg — конфигурационный файл для qSpider.
  2. Напишите диалоги в формате edsynt (см. Создание диалогов) и сгенерируйте на их основе общий файл qsps (см. Работа с генератором).
  3. Скопируйте из общего файла qsps локации в свою игру, или сконвертируйте в файл QSP и подключите, как модуль.
  4. В своей игре подключите "easy.dialog" и вспомогательные библиотеки, прописав, например, на самой первой локации:
inclib 'lib/easy.dialog.qsp'
inclib 'lib/easy.database.qsp'
inclib 'lib/easy.math.qsp'
  1. Чтобы вызвать диалог используйте команду: @dialog.int('UNIQUENAME'), — где UNIQUENAME — уникальное название диалога. Подробнее о воспроизведении диалогов в своей игре читайте в разделе Работа с интерпретатором.

Работа с исходниками

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

Следует помнить, что в этом случае вам потребуются также исходники двух других библиотек для сборки:

Обратная связь и поддержка

Если ваш диалог после генерации работает неправильно, либо вообще не генерируется, пришлите мне файл edsynt вашего диалога, чтобы я мог установить причину. Это поможет улучшению и исправлению ошибок в модуле.

Написать мне можно на aleksversus@mail.ru, или найти меня в дискорде на сервере интерактивной литературы.

Вы очень поддержите меня, если подпишетесь на мой бусти (это можно сделать бесплатно), или на канал "Пишем игры на QSP" на YouTube.

Создание диалогов

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

Разделы расположены в порядке усложнения правил написания, а не в порядке рассмотрения структуры диалога. Поэтому вкратце опишу, как сгенерированный диалог будет представлен в QSP.

Диалоги, написанные для модуля "easy.dialog", в QSP выглядят как записи в большой таблице данных, где каждая строчка таблицы является отдельной сущностью (объектом):

  • Диалогом;
  • Ролью;
  • Репликой.

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

Если вам это не понятно, то нет смысла вникать, пока не возникнет такая необходимость. Для написания простых диалогов, понимание этой структуры не требуется. Просто пишите каждый диалог в отдельном файле, конвертируйте их в формат QSP и вызывайте в игре.

edsynt

"edsynt" (дальше это слово будет без кавычек) — это самописный синтаксис, который должен облегчить написание диалогов, даже больших и сложных. Этот синтаксис используется только для работы модуля easy.dialog, так что скорее всего вы не услышите о нём за пределами данного руководства.

Подсветка edsynt для sublime-text так же поставляется вместе с релизной версией модуля, вы можете найти её в папке tools в виде готового пакета edg.sublime-package. Просто скопируйте пакет в папку Packages в место установки Sublime Text, и вам станет доступна подсветка edsynt для текстовых файлов с расширениями .edg и .edsynt.

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

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

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

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

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

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

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

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

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

Фразы, заключённые в те или другие теги, мы будем называть репликами.

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

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

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

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

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

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

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

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

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

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

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

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

{:







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






:}

То на экран всё равно будет выведено: "Как вас зовут?" без пробелов перед репликой и пустых строк. Хотя лучше, конечно, избегать избыточного преформатирования.

Назначение ролей. Форматирование реплик

Итак, мы научились составлять простейшие диалоги, используя два типа Реплик. Реплики героя воспроизводятся сначала в виде действий, и только после нажатия (активации) выводятся на экран в лог диалога, и потому вдальнейшем мы будем называть их активируемыми репликами. Реплики актёра воспроизводятся в логе автоматически, и поэтому мы будем называть их пассивными репликами.

Чтобы реплики относящиеся к разным актёрам и герою не сливались в одно монотонное месиво, нам нужно каким-либо образом обозначить, кто произносит ту или иную фразу. И с этим делом нам помогут Роли.

Роли — это специальные объекты в диалоге, которые необходимы, чтобы настроить внешний вид отображения Реплик.

Если вы не добавите Роли в свой диалог, будут использованы Роли по умолчанию.

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

Описывать Роли следует в самом начале файла с диалогом.

Первым делом перечисляем идентификаторы Ролей в специальной переменной actors.

actors="hero; npc; remarka; other; etc"

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

К идентификаторам ролей предъявляются те же требования, что и к именам переменных в QSP, c дополнительным условием: в них не должны использоваться никакие спецсимволы, включая $.

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

actors="hero; npc; remark;"
	
	<actor.hero> </actor.hero>

	<actor.npc> </actor.npc>

	<actor.remark> </actor.remark>

Внутри этих описательных блоков необходимо разместить настройки Ролей, а также блоки обёрток.

  • Для активируемых реплик, это блок, ограниченный html-тегами <wrap.btn></wrap.btn>. Он отвечает за внешний вид действий (кнопок).
  • Для пассивных и активируемых реплик, это блок, ограниченный html-тегами <wrap.frase></wrap.frase>. Он отвечает за внешний вид выводимых на экран (в лог диалога) фраз.

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

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

Помимо обёрток вы можете использовать для описания каждой Роли наборы собственных параметров (переменных). Пример:

   actors="hero; npc;"
   
      <actor.hero>
         name="Балур Светосильный"
         age="17"
      </actor.hero>

      <actor.npc>
         name="Темнейший Злеус"
         age="1069"
      </actor.npc>

Подобные переменные полностью переносятся в объект Роль, и их можно легко извлекать, что опять же демонстрируется в разделе Обёртки.

Одна из описанных Ролей должна быть помечена, как роль по умолчанию для активируемых реплик (это делается с помощью одиночного тега <default_active>), и одна роль должна быть помечена, как роль по умолчанию для пассивных реплик (это делается с помошью одиночного тега <default_passive>).

Подключение ролей

При описании Роли можно подключить к ней стороннюю Роль. При этом в исходную Роль копируются все настройки подключаемой Роли, кроме тегов, устанавливающих роль по умолчанию.

   actors="hero; npc;"
   
      <actor.hero>
         name="%$property['heroname']%"

         include_role:DIALOG.ROLE
      </actor.hero>

Здесь DIALOG — уникальное название диалога, а ROLE — уникальная метка роли в указанном диалоге.

ВНИМАНИЕ!!! Подключение ролей ещё не реализовано в генераторе! TODO: подключение ролей реализует механизм наследования для ролей, таким образом можно ссылаться на роли из других диалогов и не использовать их повторно. Однако это может быть нерационально, поскольку мы и так можем сослаться на любую роль, используя параметры actor_act, actor_pass, actor_this.

Общие настройки диалога

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

dialog_usrid. Пользовательский идентификатор диалога

Параметр dialog_usrid является обязательным для любого диалога.

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

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

Пример:

dialog_usrid="barmen"

По данному уникальному имени вы сможете вызывать диалог у себя в игре:

@dialog.int('barmen')

strings. Число фраз в логе

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

actors="hero; npc;"
   
   <actor.hero>name="%$property['heroname']%"</actor.hero>
   <actor.npc>name="%$DIALOG_VALUE['current_npc_name']%"</actor.npc>

strings:10

Вместо 10 можно указать своё число. Если в логе будет меньше фраз, выведутся все фразы из лога.

Обёртки

По сути обёртки представляют собой html-разметку, в которую заворачиваются фразы героя или NPC. Однако, реализованы обёртки, как обычный код QSP, что позволяет нам довольно гибко извлекать данные из Роли, оборачивать фразы в любую разметку, форматируя диалог так, как нам удобно.

Как уже было сказано в разделе Назначение ролей. Форматирование реплик, обёртки прописываются в роль с помощью тегов <wrap.btn></wrap.btn> и <wrap.frase></wrap.frase>, и представляют собой валидный код QSP. При написании собственной обёртки, её следует рассматривать, как функцию, которой передаются три аргумента:

  • $args[0] - идентификатор текущей роли
  • $args[1] - идентификатор родительской роли
  • $args[2] - фраза, которую необходимо вывести в лог диалога, или в виде действия (кнопки).

TODO: Механизм наследования ролей ещё не сделан. Поэтому в будущем идентификатор родительской роли может не понадобиться

Результатом выполнения кода обёртки должна быть фраза реплики, завёрнутая в HTML-код. Поэтому нужно использовать переменную $result для возвращения результата.

Вот как может выглядеть пример обёртки для выводимых в лог диалога фраз:

local $name_ = @dialog.role.get_set($args[0], 'name')
$result = '<span style="color:#880000;"><<$name_>></span>: — <<$args[2]>>'

А вот так может выглядеть обёртка для кнопок:

$result = '<div class="avs-act-button"><<$args[2]>></div>'

Для примера, вот так могут выглядеть обёртки в описании Роли:

<actor.hero>
   name="Вы"

   <default_active>
   <wrap.btn>
      !@ внешний вид кнопок для ответов.
      $result += '<div class="avs-act-button"><<$args[2]>></div>'
   </wrap.btn>
   <wrap.frase>
      !@ внешний вид реплик в логе диалога Извлекаем имя из роли:
      local $name_ = @dialog.role.get_set($args[0], 'name')
      $result += '<div class="avs-hero-replic">'
         $result += '<span style="color:#000000;"><<$name_>>:</span>'
         $result += '<span style="color:#000000;"><<$args[2]>></span>'
      $result += '</div>'
   </wrap.frase>
</actor.hero>

Настройки отображения реплик

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

Настройки отображения отдельных реплик

btn_name

btn_name: :btn_name — для активируемой реплики устанавливается указанное название кнопки. Данная настройка имеет приоритет перед act_lenght. Пример:

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

   btn_name:Никогда не думай, что ты иная...:btn_name
:]
actor_this

actor_this — ссылка на роль, применяему только к этой реплике, и не наследуемую вложенными репликами. Можно указать в следующем формате: DIALOG.ROLE, — где DIALOG — это уникальное название диалога, а ROLE — идентификатор роли в этом диалоге. Пример:

{:
   Как Вас зовут?
   {:   
      actor_this:remarka <!-- здесь указан только идентификатор роли -->
      Что Вы на это ответите?
   :}
   [:Петя:]
   [:Вася <!-- здесь указано уникальное название диалога, 
               а через точку идентификатор роли -->
      actor_this:barmen.npc
   :]
:}

Настройки отображения реплик для веток

actor_act

actor_act — ссылка на роль, которая будет применяться для всех активируемых реплик в этой ветке. Иными словами, это наследуемая настройка для реплик, вложенные реплики унаследуют эту настройку от старших. Можно указать в следующем формате: DIALOG.ROLE, — где DIALOG — это уникальное название диалога, а ROLE — идентификатор роли в этом диалоге. Пример:

{:
   actor_act:barmen.hero <!-- Все активируемые реплики в данной ветке диалога
                             будут выводиться с форматированием для роли hero
                             из диалога с уникальным названием barmen -->
   Как Вас зовут?
   {:   
      Что Вы на это ответите?
   :}
   [:Петя:]
   [:Вася:]
:}
actor_pass

actor_pass — ссылка на роль, которая будет применяться для всех пассивных реплик в этой ветке. Иными словами, это наследуемая настройка для реплик; вложенные реплики унаследуют эту настройку от старших. Можно указать в следующем формате: DIALOG.ROLE, — где DIALOG — это уникальное название диалога, а ROLE — идентификатор роли в этом диалоге. Пример:

{:
   Спрашивай, не стесняйся, у меня самый продвинутый диалог!
   {:
      actor_pass:aragorn.npc <!-- Все пассивные реплики в данной ветке диалога
                             будут выводиться с форматированием для роли npc
                             из диалога с уникальным названием aragorn -->
      [:
         Как реализован твой диалог?
         {:
            Ну, это довольно просто...
         :}
      :]
      [:
         А чем бармен с тем алкоголиком заведуют?
         {:
            Бармен показывает пример самого простого линейного диалога...
         :}
      :]
   :}
:}
btn_length

btn_length — устанавливает максимальное число символов от длины фразы активируемой реплики для названия кнопки. По умолчанию 64 символа. Иными словами, если фраза активируемой реплики превышает 128 символов в длину, движок автоматически подрежет эту фразу до 64 символов, когда будет генерировать кнопку.

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

   btn_length:50 <!-- в данном случае на кнопке будет написано:
   Никогда не думай, что ты иная, чем могла бы быт...
   -->
:]

Настройки порядка вывода реплик

Настройки порядка вывода реплик всегда прописываются для целых веток. Иными словами они являются наследуемыми; вложенные реплики наследуют настройки от старших реплик.

TODO: Порядок вывода реплик должен сбрасываться при повторном посещении диалога, но пока это не сделано. Выглядит довольно ресурсоёмко.

repeat

repeat — данная настройка регулирует количество повторов вывода пассивных реплик. Можно указать три значения:

  • one — выводится одна реплика. Например, выбранная случайно.
  • once — все реплики выводятся по одному разу, после чего выводится только последняя выведенная реплика.
  • cicle — реплики выводятся по кругу, т.е. имеют свойство повторяться (режим по умолчанию).

Пример:

[:
   Вы не знаете, как попасть в город?

   repeat:one

   {:Отвали!:}
   {:Я занят!:}
   {:Позвоните попозже...:}
:]
shuffle

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

  • random — реплики выводятся в случайном порядке.
  • straight — реплики выводятся от первой к последней, как они прописывались в файлах edsynt

Управление репликами

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

selrepl.del

selrepl.del — на время интерпретации диалога "удаляет" реплику из сеанса интерпретации. То есть, если реплика помечена этой меткой, и была один раз воспроизведена, она помечается, как скрытая, и больше не воспроизводится, только если на неё не произведут целенаправленный переход. При повторном посещении диалога реплика снова доступна до первой интерпретации.

selbtn.del

selbtn.del — удаляет кнопку из списка, но лишь пока не произойдёт повторная интерпретация ветки с репликой, помеченной этой меткой (грубо говоря удаляет действие из списка, не удаляя прочие). Используется в примере "Диалог Арагорна":

[:
   И сколько всего этих "блоков"? И какие они?
   <!-- selbtn.del -->
   {:
      Ну, первый блок, это блок приветствия. В него ты попадаешь в самом начале. Оттуда тебя "перекидывает" в основной блок вопросов, второй по счёту, а за ним идут блоки вопросов, для каждого вопроса — свой блок.
   :}
:]
btn.fix

TODO: пока не реализовано. Реплика, отмеченная данной меткой, должна фиксироваться в виде действия вверху списка реплик

selrepl.kill

selrepl.kill — реплика, помеченная этой меткой, полностью удаляется из таблицы.

TODO: Пока что удаляется только одна реплика. Но это может привести к артефактам и ошибкам. По хорошему, нужно удалять реплику, её дочерние реплики, а так же записи о ней из родительских реплик/диалогов.

frase_block

<frase_block> </frase_block> — между этими тегами размещается блок фраз. Каждая строчка такого блока считается отдельной фразой. Если нужно разделить строки внутри фразы, можно использовать html-теги вроде <br> и <p></p>.

Чтобы к строке применилась необходимая роль, в начале строки нужно использовать тег <actor:HERO>, где вместо HERO нужно указать идентификатор роли в текущем диалоге, или DIALOG.ROLE. Здесь DIALOG — уникальное название диалога, а ROLE — идентификатор роли в указанном диалоге.

Пример из "диалога Чешира":

<frase_block>
   <actor:npc>У тебя одна минута на то, чтобы объяснить мне, как добраться до хранилища.
   <actor:hero>Что?
   <actor:remarka>Бац! В глазах сверкнуло и боль такая сильная, что кажется, будто она — единственное, что я сейчас чувствую. Даже сильнее страха.
   <actor:npc>Одна минута, — <em style="color:#888888;">говорит он и прижимает холодный кружок дула к моему лбу.</em>
</frase_block>
if

<if> </if> — если нам нужно, чтобы реплика воспроизводилась только при определённом условии, между этими тегами записывается валидное условие для QSP.

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

if obj('Синий крокодил'):
   ! выводим реплику в лог
   *pl '— Я гляжу, ты везде таскаешь с собой это чудовище! Продай его мне.'
end

Ну а в edsynt этом может выглядеть так:

{:
   <if>obj('Синий крокодил')</if>
   Я гляжу, ты везде таскаешь с собой это чудовище! Продай его мне.
:}
dynamic_code

<dynamic_code> </dynamic_code> — между этими тегами размещается валидный код QSP, который будет выполнен при интерпретации реплики, перед выводом её в лог диалога.

С помощью данных тегов можно фиксировать состояние диалога во внешних переменных, изменять состояния квестов, предметов и т.п. Например, в "диалоге Арагорна" так реализована очистка окна от всех кнопок перед закрытием диалога, а так же задержка перед закрытием диалога.

[:
   Ладно, пойду я, пожалуй.
   <!-- очистка списка действий <dynamic_code>killvar '$DIALOG_BUTTONS'</dynamic_code>-->
   {:
      <!-- в оригинале перед этой репликой стоит задержка, потом вывод реплики, снова задержка, потом закрытие -->
      <!-- динамический код в этой реплике осуществит задержку, после чего она появится на экране -->
      Ну, прощай тогда!
      <!-- <dynamic_code>wait 500</dynamic_code> -->
         {:
            <!-- Эта реплика не появится на экране, она выполнит задержку и closeup -->
            <!-- <dynamic_code>wait 2000</dynamic_code> -->
         :}
   :}
:]

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

marker

marker — с помщью данного параметра устаналвиваем на реплику уникальную для всего диалога метку. В пределах одного диалога метки не должны повторяться, но между диалогами реплики могут повторяться. Если вы ошиблись и указали две одинаковые метки в диалоге, генератор сообщит вам об этом.

Пример:

{:
   marker:dialog_struckture
:}
levelup

levelup — переход по узлам наверх (назад). Например, от текущей реплики вам нужно вернуться на две реплики вверх (назад), тогда указываете:

levelup:2

TODO. Возможно, если указать большее значение, чем вложено одну в другую реплик, это вызовет ошибку, или неверную работу диалога. Лучше указывать заранее допустимые значения.

leveljump

leveljump — переход на указанную метку. Можно указать метку в текущем диалоге, или DIALOG.MARKER, где DIALOG — уникальное название диалога, а MARKER название метки в этом диалоге. В последнем случае вас фактически перебросит в другой диалог, поэтому будьте внимательны при указании меток вне текущего диалога.

Пример:

[:
   А как организован твой диалог?
   {:<!-- leveljump:block_stick -->Ну, это довольно просто. Диалог состоит из нескольких блоков, после того как ты выберешь один из вариантов, он удаляется из списка действий. Иногда удаляются все действия, а вместо них добавляются новые. Как сейчас, например.:}
:]
closeup

closeup — закрывает текущий диалог, восстанавливая окно основного описания к состоянию до диалога.

replic_app

replic_app — подключение реплики из другого диалога. Указывается DIALOG.MARKER, где DIALOG — уникальное название диалога, реплику из которого мы хотим подключить, а MARKER — метка реплики в этом диалоге.

TODO. Данная команда пока ещё не работает. Нет точного представления, как это должно функционировать. Возможно при генерации диалога, реплики из соседнего диалога будут копироваться в текущий, хотя это нерационально

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

Все реплики можно комментировать. Комментарии записываются так же, как записываются в обычном html-документе, т.е. внутри конструкции <!-- -->. При генерации таблицы диалогов все подобные конструкции будут удалены.

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

Конечно, настройки можно указывать и не в комментариях, но если разместить их внутри комментария, это гарантирует, что даже если вы ошибётесь в названии какого-либо параметра или тега, они не попадут в выводимую на экран фразу, или в название кнопки, а значит возникнет меньше вероятности, что qSpider упадёт в молоко, или в классическом плеере поломается разметка.

Запрещено вкладывать комментарии в теги <if> </if>, <dynamic_code> </dynamic_code>, <wrap.btn></wrap.btn> и <wrap.frase></wrap.frase>, а так же вкладывать комментарии в комментарии. Вот пример неправильного размещения комментариев:

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

Фразы, предназначенные для вывода в лог диалога, помещать в комментарии не нужно.

Ремарки

Ремарка - это авторский текст, который бывает нужно поместить между репликами персонажей. Специальных настроек для ремарок нет, но делать их, тем не менее, просто.

Я рекомендую завести для ремарок отдельную роль с обёрткой, не добавляющей дополнительного форматирования, а форматировать фразу уже непосредственно внутри реплики.

<!--
   dialog_usrid="testgame"
   actors="npc;hero;"

   <actor.npc>
      <default_passive>
      <wrap.frase>
         $result = '<span style="color:#008888">Ведущий: — <<$args[2]>></span>'
      </wrap.frase>
   </actor.npc>

   <actor.hero>
      ...
   </actor.hero>

   <actor.remarka>
      <wrap.frase>
         $result = '<div><<$args[2]>></div>'
      </wrap.frase>
   </actor.remarka>

-->

{:
   Как вас зовут?
   {:   
      actor_this:remarka
      <em>Что Вы на это ответите?</em>
   :}
:}

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

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

Мишель (громко): — Яичница с ветчиной, это сколько?
Мыса: — На весну оставляю, — хныча, малец потёр багровое ухо. — Голодно будет.

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

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

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

<!--
   ...

   <actor.remarka>
      <wrap.frase>
         $result = '<div><<$args[2]>></div>'
      </wrap.frase>
   </actor.remarka>

-->
{:
   ...


   {:
      actor_this:remarka
      <strong>Мишель</strong> <em>(громко)</em><strong>:</strong> — Яичница с ветчиной, это сколько?
   :}
:}

Или, если предыдущий вариант вам по каким-либо причинам не подходит (например, имя должно извлекаться из роли или из переменной), создать роль, в обёртку которой уже будет включена ремарка:

<!--
   ...

   <actor.npc_loud>
      <wrap.frase>
         local $name_ = @dialog.role.get_set($args[0], 'name')
         $result = '<strong><<$name_>></strong> <em>(громко)</em><strong>:</strong> — <<$args[2]>>'
      </wrap.frase>
   </actor.npc_loud>

-->
{:
  
   {:
      <!-- actor_this:npc_loud -->
      Яичница с ветчиной, это сколько?
   :}
:}

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

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

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

Но с чего же модуль начинает? Должна же быть какая-то реплика, с которой начинается вывод всех остальных реплик. Да. Такая реплика должна быть обязательно. Когда мы описали все настройки диалога и все применяемые Роли, мы обязательно должны открыть самую первую пассивную реплику.

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

Самая первая пассивная реплика называется корнем диалога или корневой репликой (в отличие от easy.dialog 1.0.1). Она может вообще не содержать никаких фраз, а просто заключать в себе все прочие реплики, как это сделано в "диалоге Арагорна" или в "диалоге Бармена". В "диалоге Пьяницы" эта реплика содержит в себе фразу, которая будет воспроизводиться всякий раз при вызове диалога пьяницы.

Ниже представлен пример диалога, где корневая реплика содержит фразу и некоторые настройки вывода реплик:

<!--
   dialog_usrid="testgame"
   actors="npc;hero;"

   <actor.npc>
      <default_passive>
      <wrap.frase>
         $result = '<span style="color:#008888">Ведущий: — <<$args[2]>></span>'
      </wrap.frase>
   </actor.npc>

   <actor.hero>
      <default_active>
      <wrap.frase>
         $result = 'Вася Пупкин: — <<$args[2]>>'
      </wrap.frase>
      <wrap.btn>
         $result = '<div><<$args[2]>></div>'
      </wrap.btn>
   </actor.hero>

   <actor.herogreen>
      <wrap.frase>
         $result = '<span style="color:#008800">Вася Пупкин:</span> — <<$args[2]>>'
      </wrap.frase>
      <wrap.btn>
         $result = '<div><<$args[2]>></div>'
      </wrap.btn>
   </actor.herogreen>

   <actor.herored>
      <wrap.frase>
         $result = '<span style="color:#ff0000">Вася Пупкин:</span> — <<$args[2]>>'
      </wrap.frase>
      <wrap.btn>
         $result = '<div><<$args[2]>></div>'
      </wrap.btn>
   </actor.herored>

   <actor.npcbluered>
      <wrap.frase>
         $result = '<span style="color:#008888">Ведущий:</span> <span style="color:#880000">— <<$args[2]>></span>'
      </wrap.frase>
   </actor.npcbluered>

   string:25
-->

{:
   Как вас зовут? <!-- это корень диалога.
   Здесь есть фраза, и некоторые настройки вывода реплик:
   shuffle:random
   repeat:once -->
   [:
      <!-- actor_act:herogreen <<< Здесь меняем роль для активируемых реплик -->
      Меня зовут Вася.
      {:Вас зовут Вася?
         [:
         <!-- actor_pass:npcnluered <<< Здесь меняем роль для пассивных реплик -->
         Да, меня зовут Вася.
            {:да ну нафиг:}
            [:
            нет. чистая правда
            :]
         :]
         [:Нет, никто меня так не зовёт:]
      :}
      {:
         Это правда, что вас зовут Васей?
         [:Правда
         :]
         [:Неправда:]
      :}
   :]
   [:
      <!-- actor_act:herored <<< Здесь меняем роль для активируемых реплик -->
      Меня зовут Петя.
      {:
         Вас зовут Петя?
         [:Да{:Как это удобно!:}:]
         [:
            Нет
            {:Вас зовут не Петя? Как неудобно!:}
         :]
      :}
      {:
         Это правда, что вас зовут Петей?
         [:Правда:]
         [:Неправда:]
      :}
      {:Неужели в самом деле Петей Вас зовут?
         [:В самом деле:]
         [:Нет, не в самом:]
      :}
   :]
:}

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

Когда модуль находит первую реплику диалога (его корень) и выводит её на экран (или не выводит, если фраза не прописана), он находит также и все вложенные реплики. В нашем примере, в корневой реплике будут найдены две активируемые реплики, их-то мы и увидим на экране в виде действий.

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

Мы тоже остановимся, но совсем по другой причине.

На этом примере мы увидели сразу два элемента диалога: узел и ветви.

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

Реплика, в которую не вложено ни одной другой реплики, называется финальной (последней, конечной).

Две однотипные реплики, вложенные в одну реплику, считаются (и называются) ветвями диалога. Такие реплики называются одноуровневыми, то есть находящимися на одном уровне.

Уровень реплики считается по количеству узлов, которые ей предшествовали.

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

Реплика "Как вас зовут?" из примера - нулевой уровень; реплики "Меня зовут Вася" и "Меня зовут Петя" — первый уровень; и т.д.

Чем меньше численно уровень реплики, тем она старше.

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

В модуле реализовано наследование настроек Репликами от старших Реплик. Это немного замедляет воспроизведение Реплики, однако экономит память, в которой хранится таблица далогов.

Если модулю нужно воспроизвести реплику в виде кнопки или фразы, он первым делом пытается получить настройки реплики. При этом модуль может получить как полный список настроек, так и частичный.

Если полный список настроек ещё не получен, модуль пытается извлечь настройки из родительской реплики, затем из старших реплик, и так далее. Таким образом, пока не будет получен полный список настроек, необходимых для воспроизведения текущей реплики, модуль продолжает обращаться к всё более старшим репликам.

Таким образом он может дойти до корня и до объекта диалога (Диалог можно рассматривать, как родителя корневой реплики). Если совсем никакие настройки в диалоге не были определены, модуль берёт настройки по-умолчанию, то есть те, которые в него вшиты.

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

Скрытый узел

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

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

Как избежать очистки списка кнопок

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

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

   {:да ну нафиг:}
   [:нет. чистая правда:] <!-- эта кнопка появится на экране, но кнопки старших реплик не сотрутся -->
:]

Работа с генератором

В релизной версии модуля генератор находится в папке tools/_generator.

Для того, чтобы запустить диалоги на генерацию, сначала требуется отредактировать файл easy.dialog.generator.py:

  1. В переменной dialog_files необходимо сформировать список путей к файлам, в которых написаны диалоги в формате edsynt. Пути можно указывать как в абсолютном, так и в относительном виде.
  2. В переменной output_path указываем путь к выходному файлу в формате qsps. Именно в этот файл будет записан валидный код QSP с уже сформированной таблицей диалогов.
  3. Можно отредактировать параметр split_code, если вы хотите загружать реплики в память порционно. Если указать число больше нуля, реплики будут разделены на группы и обёрнуты в конструкции условий с проверкой, равно ли args[0] определённому числу (номеру группы реплик).

После редактирования файла, запустите его в интерпретаторе python — по указанному в output_path пути появится файл с таблицей диалогов.

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

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

Работа с интерпретатором

Подключение интерпретатора

Чтобы использовать интерпретатор диалогов "easy.dialog", вам необходимо подключить его к своей игре вместе с сопутствующими библиотеками.

Если вы скачали архив со страницы релизов, то в папку со своей игрой из этого архива распакуйте папку lib. Jна и содержит модуль "easy.dialog", а так же библиотечки "easy.database" и "easy.math".

Теперь в своей игре для подключения модулей напишите эти три команды:

inclib 'lib/easy.dialog.qsp'
inclib 'lib/easy.database.qsp'
inclib 'lib/easy.math.qsp'

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

Добавление таблицы диалогов в игру

В процессе работы генератор создаёт файл формата qsps, который содержит три локации:

  • dialogs_table — на этой локации размещается вся таблица диалогов, включая все объекты диалогов, ролей и реплик. Эта локация предназначена для загрузки в память таблицы диалогов. В зависимости от настроек генератора строки таблицы на этой локации могут быть сгруппированы в блоки условий с проверкой, какое число передано нулевым аргументом (args[0]). Это число соответствует номеру группы строк. Если строки разбиты на группы, загрузить всю таблицу диалогов в память, просто обратившись к локации dialogs_table не получится.
  • dialogs_init — эта локация необходима для инициализации таблицы диалогов в базе данных игры. То есть она проводит необходимую подготовку, чтобы модуль мог работать с диалогами.
  • dialogs_load — с помощью этой локации вы можете одномоментно загрузить в память всю таблицу диалогов. Использовать её следует только если диалогов немного, или они небольшие.

Все три локации вам нужно скопировать в свою игру, или сконвертировать файл .qsps в .qsp и подключить как библиотеку.

Когда в игру подключены, или встроены все три локации, нужно сделать следующее:

  1. Необходимо инициализировать базу данных в игре, введя, например, на самой первой локации команду @edb.init. Если вы уже пользуетесь "easy.database" в своей игре, значит вы уже это сделали.
  2. Далее нужно инициализировать в базе данных таблицу диалогов, введя команду @dialogs_init.
  3. Затем таблицу диалогов нужно загрузить в память. Для этого можно воспользоваться командой @dialogs_load, или последовательно обращаясь к dialogs_table, передавая ей аргументом номер выгружаемой группы строк.

Вот как это выглядит в игре с примерами диалогов, котороя поставляется вместе с модулем:

@edb.init()
@dialogs_init()
@dialogs_load()

Воспроизведение диалога

Для воспроизведения диалога в нужном месте следует вызвать команду @dialog.int, указав аргументом уникальное название диалога, которое вы прописывали ему в файлах edsynt перед генерацией.

Пример вызова диалога из гиперссылки:

*p '<div class="avs_dialog_hide">'
   *p '<a href="EXEC: @dialog.int(''robbank'')" class="avs-plain">Начать диалог</a>'
*p '</div class="avs_dialog_hide">'

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

Встраивание диалога в Основное описание

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

Чтобы диалог не замещал собой весь текст в окне основного описания, а встраивался в него, необходимо перед воспроизведением диалога вывести в основное описание два специальных тега:

<div class="avs_dialog_div">Текст между тегами</div class="avs_dialog_div">

Обратите внимание, что классы прописаны для обоих тегов: и открывающего и зыкрывающего. Это сделано специально, чтобы не перегружать QSP при поиске тегов в тексте. Любой HTML-рендер исправит неверно записанный закрывающий тег самостоятельно.

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

Если нужно скрыть какой-то текст на время интерпретации диалога, но не выводить вместо него диалог, используйте другой специальный тег:

<div class="avs_dialog_hide"></div class="avs_dialog_hide">

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

Пример описания, в которое встраивается диалог, можно посмотреть в игре с примерами диалогов на локации место;банк в "диалоге Чешира".

TODO: Обёртка диалога. Имеет смысл рассмотреть вариант, когда вместо описания локации диалог встраивается в некую обёртку, таким образом формируется псевдоокно.

Составные части интерпретатора диалогов

Теоретически интерпретатор должен работать без вашего вмешательства в его поведение, однако иногда вмешательство необходимо. Например, в "диалоге Арагорна" через уничтожение массива $DIALOG_BUTTONS производится очистка списка кнопок перед закрытием диалога, чтобы игрок не мог ничего нажимать.

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

Общее назначение глобальных временных массивов

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

  • $DIALOG_VALUE — основной массив, сохраняющий различные настройки диалога на время сеанса.
  • $DIALOG_REPLIC_LOG — массив, сохраняющий лог диалога на время сеанса.
  • $DIALOG_BUTTONS — массив, сохраняющий список кнопок во время сеанса.

Управление через внешние глобальные переменные

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

Будьте внимательны!

  • GAME_VALUE['dialogs_log_size'] - число реплик, которые сохраняются в логе. Если не указано, то сохраняется сто реплик, включая скрытые.
  • GAME_VALUE['dialogs_timer_interval'] - интервал задержки перед выводом следующей фразы из блока фраз. Если не указано равен 500 мс.
  • $GAME_VALUE['dialogs_true_goto'] — должен содержать код, который выполнится при закрытии диалога (перед удалением временных массивов). Если код не прописан, при закрытии диалога просто очищается окно основного описания и восстанавливается тот текст, который был сохранён перед открытием диалога. Здесь может пригодиться команда goto $curloc, так как в процессе воспроизведения диалога значения некоторых переменных могут измениться, и это повлияет на описание локации.

Значения DIALOG_VALUE

Значения в DIALOG_VALUE сохраняются только на время воспроизведения диалога.

  • $DIALOG_VALUE['screen_main'] — в этой переменной сохраняется вид окна основного описания до воспроизведения диалога. Из него же потом восстанавливается текст в окне основного описания, когда диалог завершается.
  • $DIALOG_VALUE['screen_now'] — в этой переменной содержится "снимок" экрана вместе с размещённым в нём диалогом. Данный снимок обновляется при интерпретации реплики, и только после этого выводится на экран.
  • $DIALOG_VALUE['dialog_id'] — здесь временно сохраняется первичный ключ строки, в которую прописан объект Диалог.
  • $DIALOG_VALUE['root_id'] — первичный ключ корневой реплики.
  • DIALOG_VALUE['default_screen_replics_number'] — число фраз, одновременно видимых на экране. По умолчанию — 10.
  • $DIALOG_VALUE['default_replics_repeat'] — схема повторения реплик. Стандартный режим: 'cicle'.
  • $DIALOG_VALUE['default_replics_shuffle'] — порядок вывода реплик. Стандартный режим: 'straight'.
  • DIALOG_VALUE['default_buttons_length'] — Максимальная длина текста в кнопке. Стандартное значение 128.
  • DIALOG_VALUE['timer_start'] — переключатель, запускающий таймер, когда необходимо выводить фразы из блока.
  • DIALOG_VALUE['timer_next_refresh'] — в этой переменной сохраняется предельное пороговое время, после которого должна быть выведена фраза из блока фраз. Изменять не рекомендуется.
  • $DIALOG_VALUE['del_btns_list'] — список удалённых с экрана кнопок. Пока идентификатор реплики присутствует в этом списке, кнопка этой реплики не будет выводиться на экран. Переменная очищается при воспроизведении пассивной реплики.
  • $DIALOG_VALUE['del_replics_list'] — список реплик, удалённых из текущего воспроизведения диалога. Пока идентификатор реплики присутствует в этом списке, эта реплика не будет воспроизводиться. TODO: Возможно не работает для пассивных реплик Тем не менее реплики удалённые таким образом доступны для воспроизведения, если использовать их метку.
  • $DIALOG_VALUE['fix_btns_list'] — список зафиксированных реплик. TODO: Это пока не сделано. Предполагается, что можно будет фиксировать некоторые кнопки вверху списка кнопок диалога. Например, так можно реализовывать кнопку закрытия диалога, чтобы не проматывать весь диалог до финальных реплик

Назначение локаций модуля:

Назначение локаций можно посмотреть в исходном коде модуля в репозитории: https://github.com/AleksVersus/easy.dialog.

Отладчик

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

Подключение отладчика

Чтобы задействовать в своей игре встроенный в "easy.dialog" отладчик, пропишите в нужном месте, например, на самой первой локации, команду:

@dialog.usercom('on')

Соответственно для отключения отладчика достаточно выполнить команду:

@dialog.usercom('off')

Использование отладчика

Чтобы воспользоваться отладчиком, введите в строке ввода ключевое слово eid и соответствующую команду отладчика, а затем нажмите клавишу ввода ("Enter ↵").

Команды отладчика

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

  • help — вывод справки по отладчику.
  • run - выполнить следующий после run текст, как строку кода QSP. Пример:
    eid run *pl 'Яблок в корзине <<apples>>'
    При выполнении данной команды на экране появится строчка текста, с подставленным в неё значением переменной.
  • version - проверить текущую версию модуля. Версия модуля возвращается в текстовом формате.
  • clr - очистить окно дополнительного описания.
  • print — вывести в дополнительное описание всю таблицу диалогов. При больших размерах таблицы это может занять время или подвесить игру.
  • refs - вывести в окно дополнительного описание таблицу с короткими именами диалогов.
  • log - вывести в окно дополнительного описание сохранённый лог реплик (содержимое $DIALOG_REPLIC_LOG).
  • html - включить/отключить распознавание HTML в плеере.
  • INDEX_DIALOG_VALUE= — если ввести текстовый индекс, а затем поставить знак равенства, в окно дополнительного описания будет выведено значение из массива DIALOG_VALUE под указанным индексом. Используемые модулем ячейки перечисленны в разделе Значения DIALOG_VALUE. Пример. С помощью следующей команды, введённой в строку ввода, можно получить в окне дополнительного описания первичный ключ корневой реплики:
    eid root_id=

Термины и понятия, применяемые в данном руководстве

  • Тег — особая метка в тексте, отмечающая вхождение рассматриваемого текста. Теги могут быть открывающие и закрывающие, а так же одиночные. Комплекс из открывающего и закрывающего тегов может называться двойным тегом или сдвоенным тегом.
  • Содержимое тега — рассматриваемый текст, место вхождения которого обозначено тегами, или тегом. Может использоваться понятие "значение тега". Числовое значение здесь условно обозначается [#значение], а текстовое - [$значение]. Вместо слова "значение" может употребляться "число", "текст", "шестнадцатеричное число", и другие слова и словосочетания. Если содержимое тега принимает конечное число значений, это будет показано перечислением этих значений через вертикальную черту в квадратных скобках [одно значение|второе значение|третье значение].
  • Диалог — объект, включающий в себя Роли и Реплики, а так же ряд необходимых установок. Именно Диалоги воспроизводятся интерпретатором.
  • Команда — слово или группа слов, которые провоцируют выполнение определённых операций или действий, запуск механизмов.
  • Конструкция — текст, записанный по определённым правилам.
  • Реплика — текст, заключённый между тегами реплик. Может включать в себя произносимую фразу, другие теги, команды, конструкции. Реплики бывают пассивными (questions) и активируемыми (answers). Пассивные реплики как правило отдаются персонажам, с которыми взаимодействует игрок, в то время, как активируемые реплики — являются вариантами выбора, из которых игроку предложено выбирать.
  • Герой — персонаж, которым непосредственно управляет игрок.
  • Актёр — персонаж, которым игрок не может управлять непосредственно, NPC, непись.
  • Фраза — фраза героя или актёра, которая будет выведена на экран после обработки реплики. Фраза (иногда называется "текст реплики") - единственное содержимое реплики, которое не нужно облекать в специальные конструкции, или помещать между тегами.
  • Интерпретация реплики — процесс извлечения всех конструкций и фразы из реплики, а так же выполнение конструкций и команд, воспроизведение фразы в виде текста, выводимого на экран.
  • Пустая реплика — реплика, в которой отсутствует фраза.
  • Узел — реплика, в которую вложена другая реплика.
  • Родительская реплика — так называется узел по отношению к реплике, которая в него вложена. Реплика "F" - родительская реплика реплики "A". Ещё называется родителем. Всякий узел является родителем вложенной в него реплики. Родительские реплики могут также называться старшими репликами.
  • Дочерняя реплика — так называется реплика по отношению к реплике, в которую она вложена. Реплика "A" - дочерняя реплика реплики "F". Всякая реплика, вложенная в узел, является дочерней. Дочерние реплики также могут называться младшими репликами.
  • Корень — реплика, которая не вложена ни в одну другую реплику. Может называться также корневой репликой, нулевой репликой, начальной репликой.
  • Значение по умолчанию — значение, которое будет возвращено механизмом модуля "easy.dialog", если не указано, или ошибочно указано другое значение. Значения по умолчанию встроены в движок.
  • Уровень — счёт вложений реплик, степень вложения реплики. Если реплика вложена в корень - первый уровень. Если реплика вложена в реплику, вложенную в корень, - второй уровень. И так далее. Верхним (нулевым) уровнем при этом считается корень.
  • Ответвления — одноуровненвые реплики, вложенные в один и тот же узел.
  • Ветка — ответвление вместе со всеми вложенными в него репликами.
  • Финал — реплика, в которую не вложено ни одной другой реплики. Называется так же финальной репликой, конечной репликой, или последней репликой.
  • Роль — особый объект, вложенный в объект Диалог, который описывает внешний вид реплик при воспроизведении их от лица определённого персонажа.

Теги, конструкции, команды

  • {:[$текст]:} — теги, между которыми записываются реплики неиграбельных персонажей (далее: актёры, неписи, NPC, персонажи). Текст реплики выводится на экран автоматически. Теги пассивных реплик.
  • [:[$текст]:] — теги, между которыми записываются реплики персонажа, которым управляет игрок. Выводятся на экран в виде доступных игроку действий. При выборе действия, на экран выводится текст реплики. Теги активируемых реплик.
  • actors="[$список ролей]" — идентификаторы ролей текущего диалога, перечисленные через точку с запятой. Подробнее.
  • dialog_usrid="[$уникальное_название_дилога]" — пользовательский идентификатор диалога. Подробнее.
  • strings:[#число] — число фраз из лога диалога, одновременно видимых на экране. Подробнее.
  • btn_name:[$название кнопки]:btn_name — название кнопки, которое будет отображаться при интерпретации реплики. Подробнее.
  • actor_this:[$роль] — роль, применяемая к конкретной реплике, и не наследуемая вложенными репликами. Подробнее.
  • actor_act:[$роль] — роль для активируемых реплик, наследуемая всеми вложенными репликами. Подробнее.
  • actor_pass:[$роль] — роль для пассивных реплик, наследуемая всеми вложенными репликами. Подробнее.
  • btn_length:[#число] — длина надписи на кнопке, если фраза оказывается слишком длинной. Подробнее.
  • repeat:[one|onced|cycle] — схема воспроизведения пассивных реплик. Подробнее.
  • shuffle:[random|straight] — порядок воспроизведения пассивных реплик. Подробнее.
  • selrepl.del — удаление выбранной реплики на время интерпретации диалога. Подробнее.
  • selrepl.kill — безвозвратное уничтоение реплики. Подробнее.
  • selbtn.del — удаление кнопки из списка кнопок, до последующего воспроизведения. Подробнее.
  • btn.fix — фиксирование кнопки вверху списка. Подробнее.
  • <frase_block>[$блок фраз]</frase_block> — блок фраз, которые выводятся на экран последовательной серией. Подробнее.
  • <if>[$условие]</if> — условие для воспроизведения реплики. Подробнее.
  • <dynamic_code>[$валидный код QSP]</dynamic_code> — динамический код, выполняемый при воспроизведении реплики. Подробнее.
  • marker:[$метка] — уникальная пользовательская метка реплики. Подробнее.
  • levelup:[#число] — воспроизвести реплику на указанное число уровней вверх. Подробнее.
  • leveljump:[$метка] — воспроизвести реплику с указанной пользовательской репликой. Подробнее.
  • closeup — закрыть диалог. Подробнее.
  • replic_app:[$метка] — подключение к диалогу реплики с указанной меткой. Подробнее.
  • <!--[$ текст комментария]--> — комментарий внутри реплики. Подробнее.

Допустимые формы записи тегов и их значений

Значением сдвоенного тега считается весь текст с самого первого символа сразу после открывающего тега, до последнего символа включительно перед закрывающим тегом.

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

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

  • тегов реплик {: :} и [: :]
  • тегов <if></if>
  • тегов <dynamic_code></dynamic_code>
  • тегов <frase_block></frase_block>

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

  • тегов btn_name: :btn_name
  • тегов <!-- -->

Это связано всего лишь с методами распознавания содержимого.

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

Ни в коем случае нельзя включать одинаковые сдвоенные теги друг в друга. Запись типа

<frase_block>
   Фраза
   <frase_block>
      фраза фраза фраза
   </frase_block>
   Ещё одна фраза
</frase_block>

недопустима.

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

Таким образом в одиночные теги можно помещать только непробельные символы.

Однако, если одиночный тег и его содержимое заключены в скобки, причём между открывающей скобкой и тегом нет пробелов и переводов строки, в значении тега можно использовать и пробелы и переводы строки.

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

Если одиночный тег помещается в сдвоенный тег и не заключается в скобки, он должен отделяться от тегов сдвоенного тега хотя бы одним пробелом. Запись типа

<!--marker:first_branch-->

недопустима.

Можно использовать запись

<!-- marker:first_branch -->

или

<!--(marker:first_branch)-->

Заключение

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

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

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

Также выражаю отдельную благодарность Oliver'у за неугасимый интерес и тёплые слова, которые поддерживают во мне желание работать над этим и другими проектами для Quest Soft Player.

Если вы хотите поддержать проект, подпишитесь на мой бусти (это можно сделать бесплатно), а также на канал "Пишем игры на QSP" на Ютубе.

About

Модуль для простого и удобного написания диалогов для QSP

Resources

Stars

Watchers

Forks

Packages

No packages published