Вы уже знакомы с базовыми принципами работы диалогов и теперь, открывая файл диалога, должны видеть не только набор кракозябр, но и какую-никакую структуру. В этом уроке мы углубимся в изучение их устройства на примере реального диалога из игры.
Диалог с тавернщиком
Независимо от мода, который вы ковыряете, у вас должен быть файл ..\PROGRAM\DIALOGS\Common_Tavern.c - это диалог с тавернщиком.
Его наполнение может отличаться в деталях, но нас интересует только основное - как строится диалог, как достигается вариативность ответов и какие функции используются.
Его наполнение может отличаться в деталях, но нас интересует только основное - как строится диалог, как достигается вариативность ответов и какие функции используются.
Разбор этого файла мы разделим на 2 части: основную и квестовую. Сейчас мы сосредоточимся на основной - в данный момент нам важно понять как устроены диалоги в игре, научиться их читать и редактировать.
Разбирать мы будем следующий код:
Разбирать мы будем следующий код:
void ProcessCommonDialogEvent(ref NPChar, aref Link, aref Diag) {
ref PChar;
PChar = GetMainCharacter();
string NPC_Meeting;
switch(Dialog.CurrentNode) {
case "First time":
Dialog.defAni = "dialog_stay1";
Dialog.defCam = "1";
Dialog.defLinkAni = "dialog_1";
Dialog.defLinkCam = "1";
Dialog.defLinkSnd = "dialogs\woman\024";
Dialog.ani = "dialog_stay2";
Dialog.cam = "1";
Diag.TempNode = "First time";
if(NPChar.quest.meeting == "0") {
dialog.snd = "Voice\CHWI\CHWI001";
Dialog.Text = "Добро пожаловать, " + GetAddress_Form(NPChar) + ". Еда, выпивка и развлечения - все по умеренным ценам!";
Link.l1 = "Посмотрим... Я " + GetFullName(pchar) + ".";
Link.l1.go = "meeting";
NPChar.quest.meeting = "1";
Dialog.snd = "dialogs\speech\chrar001";
} else {
dialog.snd1 = "Voice\CHWI\CHWI002";
dialog.snd2 = "Voice\CHWI\CHWI003";
dialog.snd3 = "Voice\CHWI\CHWI004";
Dialog.Text = RandPhrase("Эй, " + GetAddress_Form(NPChar) + " " + PChar.name + "! " + TimeGreeting() + ".",
"О, какие у нас гости! Рад видеть вас, " + GetAddress_Form(NPChar) + " " + PChar.name + ".",
"Хорошо, что вы заглянули ко мне, " + PChar.name + ". Каков будет сегодня ваш заказ?", &dialog, dialog.snd1, dialog.snd2, dialog.snd3);
Link.l1 = "Есть на острове парни, готовые уйти в плавание?";
Link.l1.go = "crew hire";
Link.l2 = "Я надеюсь ты сможешь ответить на пару вопросов.";
Link.l2.go = "quest lines";
link.l3 = pcharrepphrase("Есть ли в твоем клоповнике свободная конура?", "Нет ли у тебя свободной комнаты, я хотел"+ GetSexPhrase("","а") +" бы остановиться здесь на некоторое время.");
link.l3.go = "room";
Link.l4 = "Увы, я уже ухожу, " + NPChar.name + ". До встречи.";
Link.l4.go = "exit";
}
break;
case "exit":
Diag.CurrentNode = Diag.TempNode;
NPChar.quest.meeting = NPC_Meeting;
DialogExit();
break;
case "crew hire":
if (sti(Pchar.Ship.Type) == SHIP_NOTUSED || Pchar.location.from_sea != NPChar.from_sea) {
if (NPChar.from_sea != "") {
Dialog.text = "А на что тебе матросы? Что-то не вижу твоего корабля в порту.";
link.l1 = RandPhraseSimple("Точно... я его пришвартовал"+ GetSexPhrase("","а") +" не в том месте", "Забыл"+ GetSexPhrase("","а") +" войти в порт...");
link.l1.go = "exit";
break;
}
}
if (makeint(environment.time) > 22.0 || makeint(environment.time) < 10.0) {
dialog.snd = "Voice\CHWI\CHWI011";
Dialog.text = "Обычно у меня в таверне полно людей, желающих стать матросами, но сейчас слишком поздно, и они начнут появляться только утром. Может быть, вы хотите снять комнату, и подождать их?";
link.l1 = "Хорошо. У тебя есть свободные комнаты?";
link.l1.go = "room";
link.l2 = "Меня не интересует комната на данный момент. Давай сменим тему.";
link.l2.go = "no quest";
} else {
if (GetNationRelation2MainCharacter(sti(NPChar.nation)) == RELATION_ENEMY && sti(NPChar.nation) != PIRATE) {
Dialog.text = "А ты думаешь к тебе кто-то пойдет, когда ты с нами во вражде? Скажи спасибо, что стражу не зову.";
link.l1 = "Спасибо.";
link.l1.go = "exit";
} else {
Diag.CurrentNode = Diag.TempNode;
NPChar.quest.meeting = NPC_Meeting;
DialogExit();
LaunchHireCrew();
}
}
break;
}
}
Начало нам уже знакомо, за исключением того, что в обработчик явно передаются ссылки, которые мы ранее объявляли в начале: ProcessCommonDialogEvent(ref NPChar, aref Link, aref Diag). Связано это с тем, что данный диалог - общий для нескольких персонажей. Внимательный читатель мог обратить внимание на слово Common в названии метода. Да, это другой метод и вызывается он немного иначе, но эта тема выходит за рамки данного урока.
Далее мы создаём алиас для главного героя и из нового лишь переменная NPC_Meeting - о её роли мы ещё поговорим. Следом идёт конструкция switch и открывает её уже знакомый нам кейс "First time". С него и начнём:
Далее мы создаём алиас для главного героя и из нового лишь переменная NPC_Meeting - о её роли мы ещё поговорим. Следом идёт конструкция switch и открывает её уже знакомый нам кейс "First time". С него и начнём:
case "First time":
Dialog.defAni = "dialog_stay1";
Dialog.defCam = "1";
Dialog.defLinkAni = "dialog_1";
Dialog.defLinkCam = "1";
Dialog.defLinkSnd = "dialogs\woman\024";
Dialog.ani = "dialog_stay2";
Dialog.cam = "1";
Diag.TempNode = "First time";
// ...
break;
В самом начале мы видим остатки былой роскоши от Пиратов Карибского Моря - эти функции задают анимацию портрета персонажа и воспроизведение озвучки, которой никогда небыло ни в предыдущих, ни в последующих частях и модификациях.
Поэтому, если у вас под рукой находится мод, основанный на Корсарах 3 - скорее всего, таких строк вы ни в одном диалоге не найдёте.
Я не буду заострять здесь внимание, потому что полноценно это работает лишь в оригинальном ПКМ. Хотите разобраться в тонкостях - изучайте файлы диалогов персонажей связанных с базовым сюжетом.
Если вы модифицируете ВМЛ или что-то на нём основанное - из всего этого добра будет лишь частично работать анимация портрета. Можете прописывать только её, либо не писать вообще ничего из этого блока.
Поэтому, если у вас под рукой находится мод, основанный на Корсарах 3 - скорее всего, таких строк вы ни в одном диалоге не найдёте.
Я не буду заострять здесь внимание, потому что полноценно это работает лишь в оригинальном ПКМ. Хотите разобраться в тонкостях - изучайте файлы диалогов персонажей связанных с базовым сюжетом.
Если вы модифицируете ВМЛ или что-то на нём основанное - из всего этого добра будет лишь частично работать анимация портрета. Можете прописывать только её, либо не писать вообще ничего из этого блока.
Из всего данного блока поговорим мы только о последней строке - Diag.TempNode = "First time";
В первом уроке я говорил, что атрибуту TempNode присваивается значение "First time" еще при создании персонажа. Но если вы уже листали какие-то файлы диалогов, то наверняка заметили, что ваши предшественники-мододелы присваивают данное значение чуть ли не в каждом кейсе.
Зачем они это делают?
Если полистать диалоги с оригинального сюжета, можно заметить, что иногда атрибуту TempNode присваиваются другие значения (чаще всего "Second time"). Таким образом достигалась разная реакция на ГГ в зависимости от развития сюжета, с разными возможностями в диалоге. А чтобы не держать в голове эти исключения и всё работало как ожидается - разработчики явно задают значение TempNode в каждом кейсе.
Как правило, мододелы для всех этих ветвлений используют условные конструкции if/then/else, но большинство из них продолжает везде вставлять Diag.TempNode = "First time";.
Каким методом пользоваться и вставлять ли везде данное объявление - решать только вам.
В первом уроке я говорил, что атрибуту TempNode присваивается значение "First time" еще при создании персонажа. Но если вы уже листали какие-то файлы диалогов, то наверняка заметили, что ваши предшественники-мододелы присваивают данное значение чуть ли не в каждом кейсе.
Зачем они это делают?
Если полистать диалоги с оригинального сюжета, можно заметить, что иногда атрибуту TempNode присваиваются другие значения (чаще всего "Second time"). Таким образом достигалась разная реакция на ГГ в зависимости от развития сюжета, с разными возможностями в диалоге. А чтобы не держать в голове эти исключения и всё работало как ожидается - разработчики явно задают значение TempNode в каждом кейсе.
Как правило, мододелы для всех этих ветвлений используют условные конструкции if/then/else, но большинство из них продолжает везде вставлять Diag.TempNode = "First time";.
Каким методом пользоваться и вставлять ли везде данное объявление - решать только вам.
Что происходит дальше:
switch(Dialog.CurrentNode) {
case "First time":
// ...
if(NPChar.quest.meeting == "0") { // проверяем, знаком ли ГГ с барменом
dialog.snd = "Voice\CHWI\CHWI001";
Dialog.Text = "Добро пожаловать, " + GetAddress_Form(NPChar) + ". Еда, выпивка и развлечения - все по умеренным ценам!";
Link.l1 = "Посмотрим... Я " + GetFullName(pchar) + ".";
Link.l1.go = "meeting";
NPChar.quest.meeting = "1"; // помечаем, что мы познакомились
Dialog.snd = "dialogs\speech\chrar001";
} else {
dialog.snd1 = "Voice\CHWI\CHWI002"; // выбор файлов озвучки
dialog.snd2 = "Voice\CHWI\CHWI003"; // работает только в оригинальной ПКМ
dialog.snd3 = "Voice\CHWI\CHWI004";
Dialog.Text = RandPhrase("Эй, " + GetAddress_Form(NPChar) + " " + PChar.name + "! " + TimeGreeting() + ".",
"О, какие у нас гости! Рад видеть вас, " + GetAddress_Form(NPChar) + " " + PChar.name + ".",
"Хорошо, что вы заглянули ко мне, " + PChar.name + ". Каков будет сегодня ваш заказ?", &dialog, dialog.snd1, dialog.snd2, dialog.snd3);
Link.l1 = "Есть на острове парни, готовые уйти в плавание?";
Link.l1.go = "crew hire";
Link.l2 = "Я надеюсь ты сможешь ответить на пару вопросов.";
Link.l2.go = "quest lines";
link.l3 = pcharrepphrase("Есть ли в твоем клоповнике свободная конура?", "Нет ли у тебя свободной комнаты, я хотел"+ GetSexPhrase("","а") +" бы остановиться здесь на некоторое время.");
link.l3.go = "room";
Link.l4 = "Увы, я уже ухожу, " + NPChar.name + ". До встречи.";
Link.l4.go = "exit";
}
break;
}
А дальше нас встречает то, о чем я только-что говорил: вместо принципиально другой ноды диалога здесь используется условный оператор if, который проверяет значение переменной meeting.
Как можно догадаться из названия, отвечает она за приветствие. Если мы в этой таверне впервые - активируется первый блок, в котором бармен и ГГ представляются и обмениваются приветствиями.
Далее переменной meeting присваивается значение 1 и при всех последующих обращениях к данному бармену выводится стандартный диалог с тавернщиком из второго блока - else.
Как можно догадаться из названия, отвечает она за приветствие. Если мы в этой таверне впервые - активируется первый блок, в котором бармен и ГГ представляются и обмениваются приветствиями.
Далее переменной meeting присваивается значение 1 и при всех последующих обращениях к данному бармену выводится стандартный диалог с тавернщиком из второго блока - else.
Детальнее рассмотрим этот блок.
Еще в предыдущем уроке вы могли заметить, что реплики как НПЦ (Dialog.Text), так и наши варианты ответов (link.l4) состоят не только из текста, но и каких-то выражений. Самое время о них поговорить.
В большинстве случаев это просто атрибуты, такие как имя персонажа PChar.name или название корабля PChar.Ship.Name. Но не редко встречаются и функции, как RandPhrase(string a, string b, string c) в данном случае.
Это вспомагательные ф-ции для работы с текстом, их довольно много и мы поговорим об этом в следующем уроке. Конкретно эта выбирает случайную строку из трёх переданных в функцию.
Еще в предыдущем уроке вы могли заметить, что реплики как НПЦ (Dialog.Text), так и наши варианты ответов (link.l4) состоят не только из текста, но и каких-то выражений. Самое время о них поговорить.
В большинстве случаев это просто атрибуты, такие как имя персонажа PChar.name или название корабля PChar.Ship.Name. Но не редко встречаются и функции, как RandPhrase(string a, string b, string c) в данном случае.
Это вспомагательные ф-ции для работы с текстом, их довольно много и мы поговорим об этом в следующем уроке. Конкретно эта выбирает случайную строку из трёх переданных в функцию.
Далее идут наши варианты ответов: нанять команду, поговорить о задании, снять комнату и попрощаться.
В пределах этого урока мы поглядим на диалог найма команды. Он достаточно информативен и, в то же время, не сильно перегружен.
В пределах этого урока мы поглядим на диалог найма команды. Он достаточно информативен и, в то же время, не сильно перегружен.
case "crew hire":
// проверка на наличие корабля в порту
if (sti(Pchar.Ship.Type) == SHIP_NOTUSED || Pchar.location.from_sea != NPChar.from_sea) {
if (NPChar.from_sea != "") {
Dialog.text = "А на что тебе матросы? Что-то не вижу твоего корабля в порту.";
link.l1 = RandPhraseSimple("Точно... я его пришвартовал"+ GetSexPhrase("","а") +" не в том месте", "Забыл"+ GetSexPhrase("","а") +" войти в порт...");
link.l1.go = "exit";
break;
}
}
// проверка на время суток
if (makeint(environment.time) > 22.0 || makeint(environment.time) < 10.0) {
dialog.snd = "Voice\CHWI\CHWI011";
Dialog.text = "Обычно у меня в таверне полно людей, желающих стать матросами, но сейчас слишком поздно, и они начнут появляться только утром. Может быть, вы хотите снять комнату, и подождать их?";
link.l1 = "Хорошо. У тебя есть свободные комнаты?";
link.l1.go = "room";
link.l2 = "Меня не интересует комната на данный момент. Давай сменим тему.";
link.l2.go = "no quest";
} else {
// проверка на отношение нации к ГГ
if (GetNationRelation2MainCharacter(sti(NPChar.nation)) == RELATION_ENEMY && sti(NPChar.nation) != PIRATE) {
Dialog.text = "А ты думаешь к тебе кто-то пойдет, когда ты с нами во вражде? Скажи спасибо, что стражу не зову.";
link.l1 = "Спасибо.";
link.l1.go = "exit";
} else {
Diag.CurrentNode = Diag.TempNode;
NPChar.quest.meeting = NPC_Meeting;
DialogExit();
// вызов окна найма команды
LaunchHireCrew();
}
}
break;
Что мы здесь видим? Уже привычные нам элементы диалога обёрнуты во множество проверок.
Собственно, так большинство диалогов в игре и строится. Разработчики оригинальных Корсаров на каждый случай использовали отдельную ноду, а мододелы просто используют проверки. Нельзя сказать какой из вариантов правильный, а какой нет - они оба работают отлично. Делайте так, как вам удобно.
Так как все элементы приведенного кода вам уже знакомы, вкратце пробежимся по ключевым местам.
Мы пришли нанимать команду на корабль. А что если у нас нет корабля? Или он пришвартован где-то вообще на другом острове?
Первым делом, проверяем эти пункты.
Следом идет проверка на время суток. Кто же пойдёт устраиваться на работу в три часа ночи?
И в заключение - проверка на отношение нации с главным героем. Может мы вражеский шпион, пробравшийся в город под чужим флагом.
И только пройдя все эти проверки мы добираемся до ф-ции LaunchHireCrew(), которая вызывает окно найма команды.
По такому же принципу работают магазин, верфь и все остальные. Для всех их имеются аналогичные ф-ции. Мы поговорим о них в следующем уроке.
Собственно, так большинство диалогов в игре и строится. Разработчики оригинальных Корсаров на каждый случай использовали отдельную ноду, а мододелы просто используют проверки. Нельзя сказать какой из вариантов правильный, а какой нет - они оба работают отлично. Делайте так, как вам удобно.
Так как все элементы приведенного кода вам уже знакомы, вкратце пробежимся по ключевым местам.
Мы пришли нанимать команду на корабль. А что если у нас нет корабля? Или он пришвартован где-то вообще на другом острове?
Первым делом, проверяем эти пункты.
Следом идет проверка на время суток. Кто же пойдёт устраиваться на работу в три часа ночи?
И в заключение - проверка на отношение нации с главным героем. Может мы вражеский шпион, пробравшийся в город под чужим флагом.
И только пройдя все эти проверки мы добираемся до ф-ции LaunchHireCrew(), которая вызывает окно найма команды.
По такому же принципу работают магазин, верфь и все остальные. Для всех их имеются аналогичные ф-ции. Мы поговорим о них в следующем уроке.
Если у вас остались какие-либо вопросы - вы можете задать их в коментариях или разделе Q/A.