Miranda Dialog Creator - программа от создателей мода Das Mirandadorf для создания скриптов под Готику и Готику 2 Ночь Ворона.
Здесь я хочу предложить уважаемым готам и модостроителям небольшой FAQ по использованию этой замечательной программы. FAQ по большей части - перевод на русский язык оригинального readme, но я постарался добавить немного примеров и картинок, для людей, у которых мало времени заморачиваться на техническом аспекте дела - для непосредственно творческих людей. :)
Последняя версия программы на момент написания этого FAQ - 1.2.
Здесь и дальше я исхожу из того, что программа находится в том же каталоге, что и создаваемые примеры скриптов.
Создайте сразу в этом каталоге подпапку sripts, для дальнейшей работы.
У меня это выглядит так, к примеру:
Давайте начнем сразу с небольшого примера.
Создаем в любимом текстовом редакторе файл с названием, к примеру, example1.dia. Сохраним его тут же, в рабочем каталоге. Вот его содержимое:
person: NONE_101_Gustaf, 16 info: SimpleDialog > Доброе утро, Густав! < И тебе того же, коли не шутишь...
Запускаем командную строку, или любимый файловый менеджер, неважно какой - лишь бы в нем была возможность ввести и выполнить следующую команду:
MirandaDialogCreator.jar example1.dia scripts
Первый пункт - это имя самой программы. Второй - имя файла, который она будет «разбирать». Третий (scripts) - имя папки, в которую будут складываться готовые скрипты, сформированные программой.
Если все прошло нормально, появится окно лога программы. Например:
Видим, что все прошло нормально.
Идем в папку scripts. Что мы видим здесь? А видим, что сформирован файлик, названный по стандартам готических скриптов.
Имя NONE_101_Gustaf взято из нашего DIA-файла. За это отвечает строка
person: NONE_101_Gustaf, 16
Что же внутри готового файла скрипта?
instance NONE_101_Gustaf_SimpleDialog (C_INFO) { npc = NONE_101_Gustaf; condition = NONE_101_Gustaf_SimpleDialog_Condition; information = NONE_101_Gustaf_SimpleDialog_Info; important = FALSE; permanent = FALSE; description = " Доброе утро, Густав!"; }; func int NONE_101_Gustaf_SimpleDialog_Condition() { return TRUE; }; func void NONE_101_Gustaf_SimpleDialog_Info() { AI_Output(other, self, "NONE_101_Gustaf_SimpleDialog_Info_15_01"); // Доброе утро, Густав! AI_Output(self, other, "NONE_101_Gustaf_SimpleDialog_Info_16_02"); // И тебе того же, коли не шутишь... };
Клево. Сфомирован полностью готовый и рабочий пункт диалога. Без механической работы по написанию и copy/paste. Всего четырьмя строками исходного файла. Это воодушевляет. :)
person: NONE_101_Gustaf, 16
Оператор person определяет скриптовое имя персонажа, для которого пишется диалог. В данном случае это NPC, который участвует в скриптах под именем NONE_101_Gustaf.
Первый параметр - скриптовое имя, используется так:
instance NONE_101_Gustaf_SimpleDialog (C_INFO)npc = NONE_101_Gustaf; condition = NONE_101_Gustaf_SimpleDialog_Condition; information = NONE_101_Gustaf_SimpleDialog_Info;
Второй параметр (число 16) - это номер голоса, которым будет говорить наш персонаж. Конкретное использование этого числа - в функции AI_Output, к примеру, в нашем диалоге:
AI_Output(other, self, "NONE_101_Gustaf_SimpleDialog_Info_15_01"); // Доброе утро, Густав!
Нужно сразу отметить, что по по умолчанию программы ГГ говорит голосом № 15. Но это можно изменить, просто поставив в начале файла оператор
person: PC_Hero, 4
Это укажет программе использовать для функции AI_Output число 4 ( _04_XX). Этот оператор не влияет больше ни на что, кроме номера голоса.
Также сразу скажу, что в одном DIA-файле можно использовать несколько операторов person - в этом случае программа просто «распределит» скрипты разных персонажей по разным D-файлам. Но если диалоги большие, лучше бы, конечно, делать отдельный DIA-файл для каждого NPC. На любителя, в общем.
info: SimpleDialog
Оператор info - основной оператор формирования диалогов. Именно он отвечает за «появление» в готовых скриптах конструкции типа
instance NONE_101_Gustaf_SimpleDialog (C_INFO)
У него один параметр - имя диалога. В нашем случае это SimpleDialog.
Для описания простого диалога достаточно одного такого оператора, что мы и видим в примере.
Эти операторы отвечают за сам «разговор». А точнее - именно они формируют конструкцию AI_Output в файле готового скрипта. В нашем примере это очевидно. Вот «исходник»:
> Доброе утро, Густав! < И тебе того же, коли не шутишь...
А получилось:
func void NONE_101_Gustaf_SimpleDialog_Info() { AI_Output(other, self, "NONE_101_Gustaf_SimpleDialog_Info_15_01"); // Доброе утро, Густав! AI_Output(self, other, "NONE_101_Gustaf_SimpleDialog_Info_16_02"); // И тебе того же, коли не шутишь... };
Оператор »>» (больше) - это обращение ГГ к персонажу, с которым ведется диалог. А оператор »<» (меньше) - наоборот, обращение персонажа к ГГ, все очень просто. :)
Этот оператор «ставит» флаги в instance диалога, такие как important или permanent.
Флаг important - отвечает за то, чтобы персонаж сам обращался к ГГ, не дожидаясь, пока игрок начнет разговор.
Простой пример (example2.dia:
person: NONE_101_Gustaf, 16 info: ImportantDialog flags: important < Эй, погоди-ка, парень! > Ну что тебе надо опять?
А вот что получилось:
instance NONE_101_Gustaf_ImportantDialog (C_INFO) { npc = NONE_101_Gustaf; condition = NONE_101_Gustaf_ImportantDialog_Condition; information = NONE_101_Gustaf_ImportantDialog_Info; important = TRUE; permanent = FALSE; }; func int NONE_101_Gustaf_ImportantDialog_Condition() { return TRUE; }; func void NONE_101_Gustaf_ImportantDialog_Info() { AI_Output(self, other, "NONE_101_Gustaf_ImportantDialog_Info_16_01"); // Эй, погоди-ка, парень! AI_Output(other, self, "NONE_101_Gustaf_ImportantDialog_Info_15_02"); // Ну что тебе надо опять? };
Здесь Густав сам обратится к проходящему мимо него ГГ.
Флаг permanent - отвечает за то, чтобы этот пункт диалога не исчезал после разговора, а оставался в меню выбора постоянно.
Простой пример (example3.dia):
person: NONE_101_Gustaf, 16 Info: PermanentDialog flags: permanent > Мое почтение! >!t_GreetNov < Ну-ну, подлиза. Говори уже - чего надо? <!t_GetLost
А вот что получится:
instance NONE_101_Gustaf_PermanentDialog (C_INFO) { npc = NONE_101_Gustaf; condition = NONE_101_Gustaf_PermanentDialog_Condition; information = NONE_101_Gustaf_PermanentDialog_Info; important = FALSE; permanent = TRUE; description = " Мое почтение!"; }; func int NONE_101_Gustaf_PermanentDialog_Condition() { return TRUE; }; func void NONE_101_Gustaf_PermanentDialog_Info() { AI_Output(other, self, "NONE_101_Gustaf_PermanentDialog_Info_15_01"); // Мое почтение! AI_PlayAni(other, "T_GREETNOV"); AI_Output(self, other, "NONE_101_Gustaf_PermanentDialog_Info_16_02"); // Ну-ну, подлиза. Говори уже - чего надо? AI_PlayAni(self, "T_GETLOST"); };
Этот диалог будет доступен всегда.
Флаг permanent обычно используется в паре с флагом trade - формируя постоянно доступный пункт диалога торговли.
Пример (example4.dia):
person: NONE_101_Gustaf, 16 Info: LetsTrade flags: permanent, trade Num: 998 > Поторгуем?
А вот что получится:
instance NONE_101_Gustaf_LetsTrade (C_INFO) { npc = NONE_101_Gustaf; condition = NONE_101_Gustaf_LetsTrade_Condition; information = NONE_101_Gustaf_LetsTrade_Info; important = FALSE; permanent = TRUE; nr = 998; description = " Поторгуем?"; trade = TRUE; }; func int NONE_101_Gustaf_LetsTrade_Condition() { return TRUE; }; func void NONE_101_Gustaf_LetsTrade_Info() { AI_Output(other, self, "NONE_101_Gustaf_LetsTrade_Info_15_01"); // Поторгуем? };
Операторы этого типа формируют условия, которые определяют, будет показан конкретный пункт диалога или нет.
Оператор conddia - проверка на то, говорил ли ГГ уже с данным NPC по какой-либо теме (был ли выбран и отображен конкретный пункт диалога); технически - формирование функции Npc_KnowsInfo().
Пример (example5.dia):
person: NONE_101_Gustaf, 16 info: SimpleCond conddia: SimpleDialog conddia: NONE_102_Peter, IAwaitedYou > Привет. Я от Петера, только что с ним поговорил. < Хорошо. Давай к делу теперь.
А вот что получится (приведу здесь только функцию _Condition):
func int NONE_101_Gustaf_SimpleCond_Condition() { if ((Npc_KnowsInfo(hero, NONE_101_Gustaf_SimpleDialog)) && (Npc_KnowsInfo(hero, NONE_102_Peter_IAwaitedYou))) { return TRUE; }; };
Как видно из примера, можно формировать как простые, так и составные условия. Также видно, что у оператора conddia есть параметры.
Если указать только один параметр - имя instance диалога, на которое нужно провести сравнение, то в Npc_KnowsInfo подставляется имя текущего NPC - для которого пишется данный диалог.
А если указать перед именем instance диалога, на которое нужно провести сравнение, еще и имя другого NPC, то в Npc_KnowsInfo подставится это имя.
Что касается логики - два отдельных оператора cond* будут связаны логическим оператором «И».
Оператор conditem - проверка на наличие определенных предметов.
Пример (example6.dia):
person: NONE_101_Gustaf, 16 info: SimpleCond2 conditem: ItFoMuttonRaw, 4 > Я принес тебе то, что ты хотел. >>ItFoMuttonRaw, 4 < Оо, йа-йа, зер гут, Вольдемар Иванович! < Вот тебе твоя награда. <<ItFoBooze <<ItFoLoaf, 2
А вот что получится:
instance NONE_101_Gustaf_SimpleCond2 (C_INFO) { npc = NONE_101_Gustaf; condition = NONE_101_Gustaf_SimpleCond2_Condition; information = NONE_101_Gustaf_SimpleCond2_Info; important = FALSE; permanent = FALSE; description = " Я принес тебе то, что ты хотел."; }; func int NONE_101_Gustaf_SimpleCond2_Condition() { if (Npc_HasItems(other, ItFoMuttonRaw) >= 4) { return TRUE; }; }; func void NONE_101_Gustaf_SimpleCond2_Info() { AI_Output(other, self, "NONE_101_Gustaf_SimpleCond2_Info_15_01"); // Я принес тебе то, что ты хотел. B_GiveInvItems(other, self, ItFoMuttonRaw, 4); AI_Output(self, other, "NONE_101_Gustaf_SimpleCond2_Info_16_02"); // Оо, йа-йа, зер гут, Вольдемар Иванович! AI_Output(self, other, "NONE_101_Gustaf_SimpleCond2_Info_16_03"); // Вот тебе твоя награда. CreateInvItems(self, ItFoBooze, 1); B_GiveInvItems(self, other, ItFoBooze, 1); CreateInvItems(self, ItFoLoaf, 2); B_GiveInvItems(self, other, ItFoLoaf, 2); };
Как видим, использование этого оператора - проще некуда.
Одно неприятно - два отдельных оператора conditem всегда будут связаны логикой «И». А вот как сделать их связку логикой «ИЛИ», я пока не нашел.
Кстати, «всплыли» еще два оператора - »»» - ГГ отдает персонажу определенный/е предмет/ы, и »«» - NPC отдает ГГ определенный/е предмет/ы.
Сразу приведу пример (example7.dia):
person: NONE_101_Gustaf, 16 info: SimpleCond3 conddia: SimpleDialog; Pers_203_Hark, IAwaitedYou conddia: !SimpleCond2 conditem: !ItFo_Potion_GruenerTrank; conditem: ItFoMuttonRaw, 4 condcode: Npc_GetDistToWP(self, "abbc") < 500 > Я, к сожалению, не смог найти все. < Тогда ищи дальше!
Как видим, можно использовать оператор »!», чтобы ставить условие «НЕ» на операторы cond*.
func int NONE_101_Gustaf_SimpleCond3_Condition() { if ((Npc_KnowsInfo(hero, NONE_101_Gustaf_SimpleDialog) || Npc_KnowsInfo(hero, Pers_203_Hark_IAwaitedYou)) && (!Npc_KnowsInfo(hero, NONE_101_Gustaf_SimpleCond2)) && (Npc_HasItems(other, ItFo_Potion_GruenerTrank;) < 1) && (Npc_HasItems(other, ItFoMuttonRaw) >= 4) && (Npc_GetDistToWP(self, "abbc") < 500)) { return TRUE; }; };
Также, оператор conddia в явном виде поддерживает условие «ИЛИ» - просто нужно поставить »;» между параметрами.
А еще появился новый оператор - condcode. Его смысл - просто подстановка «чистого» кода скриптов готик в формируемое условие. Из примера все видно очень хорошо.
Пока не совсем понял назначение оператора condfunc, поэтому сейчас пропущу его описание. Может, кто подскажет, потом.
Еще один пример (example9.dia):
person: NONE_101_Gustaf, 16 Info: DescDialog desc: Официальное приветствие $IF Npc_GetTrueGuild(hero) == GIL_NONE >!t_GreetNov $ELSE >!t_GreetGrd $ENDIF >Hello! !Npc_ExchangeRoutine(self, "StandEntry"); $END
Результат (только функция _info):
func void NONE_101_Gustaf_DescDialog_Info() { if (Npc_GetTrueGuild(hero) == GIL_NONE) { AI_PlayAni(other, "T_GREETNOV"); } else { AI_PlayAni(other, "T_GREETGRD"); }; AI_Output(other, self, "NONE_101_Gustaf_DescDialog_Info_15_01"); // Hello! Npc_ExchangeRoutine(self, "StandEntry"); AI_StopProcessInfos(self); };
Как видно из примера, можно использовать конструкцию $IF .. $ELSE .. $ENDIF
Причем, как я понял, в параметре $IF нужно ставить скрипт готики, а не операторы MDC (Miranda Dialog Creator). В блока же можно применять как операторы MDC, так и скриптовые конструкции готики. В этом случае в начале строки нужно просто поставить оператор »!». Это дает возможность писать скриптами готики прямо в DIA-файле - все, что оформлено таким образом, попадет в файл-результат без изменений.
И еще один пример (example10.dia):
person: NONE_101_Gustaf, 16 info: Exit
Результат:
instance NONE_101_Gustaf_Exit (C_INFO) { npc = NONE_101_Gustaf; condition = NONE_101_Gustaf_Exit_Condition; information = NONE_101_Gustaf_Exit_Info; important = FALSE; permanent = TRUE; nr = 999; description = DIALOG_ENDE; }; func int NONE_101_Gustaf_Exit_Condition() { return TRUE; }; func void NONE_101_Gustaf_Exit_Info() { AI_StopProcessInfos(self); };
Краткость изначальной конструкции и результат - просто поражают. :) В общем, чтобы сделать стандартный пункт диалога «Закончить разговор», нужно просто написать вышеприведенный оператор с параметром Exit. И все.
Реализация ветвления диалогов через конструкцию Info_AddChoice - довольно удобный инструмент, но уж больно много кода приходится иногда писать. MDC спасает нас и здесь. :)
Простой пример (example11.dia):
person: NONE_102_Peter, 11 info: IAwaitedYou flags: important < Давай-ка, поиграем в угадайку. < Вот скажи, кто сильней - кит или слон? % opt: optKit > Кит? < Нет, не угадал. opt: optSlon desc: Слон, наверное? > Эээ... Слон, наверное, так? < Конечно! %% opt: Back
Результат:
instance NONE_102_Peter_IAwaitedYou (C_INFO) { npc = NONE_102_Peter; condition = NONE_102_Peter_IAwaitedYou_Condition; information = NONE_102_Peter_IAwaitedYou_Info; important = TRUE; permanent = FALSE; }; func int NONE_102_Peter_IAwaitedYou_Condition() { return TRUE; }; func void NONE_102_Peter_IAwaitedYou_Info() { AI_Output(self, other, "NONE_102_Peter_IAwaitedYou_Info_11_01"); // Уф, дядя, я уже тебя заждался совсем! Где тебя носит?! AI_Output(self, other, "NONE_102_Peter_IAwaitedYou_Info_11_02"); // Давай-ка, поиграем в угадайку. AI_Output(self, other, "NONE_102_Peter_IAwaitedYou_Info_11_03"); // Вот скажи, кто сильней - кит или слон? Info_ClearChoices(NONE_102_Peter_IAwaitedYou); Info_AddChoice(NONE_102_Peter_IAwaitedYou, " Кит?", NONE_102_Peter_IAwaitedYou_optKit); Info_AddChoice(NONE_102_Peter_IAwaitedYou, "Слон, наверное?", NONE_102_Peter_IAwaitedYou_optSlon); Info_AddChoice(NONE_102_Peter_IAwaitedYou, DIALOG_BACK, NONE_102_Peter_IAwaitedYou_Back); }; func void NONE_102_Peter_IAwaitedYou_optKit() { AI_Output(other, self, "NONE_102_Peter_IAwaitedYou_optKit_15_01"); // Кит? AI_Output(self, other, "NONE_102_Peter_IAwaitedYou_optKit_11_02"); // Нет, не угадал. }; func void NONE_102_Peter_IAwaitedYou_optSlon() { AI_Output(other, self, "NONE_102_Peter_IAwaitedYou_optSlon_15_01"); // Эээ... Слон, наверное, так? AI_Output(self, other, "NONE_102_Peter_IAwaitedYou_optSlon_11_02"); // Конечно, глупый ты дяденька! Info_ClearChoices(NONE_102_Peter_IAwaitedYou); }; func void NONE_102_Peter_IAwaitedYou_Back() { Info_ClearChoices(NONE_102_Peter_IAwaitedYou); };
В общем, если говорить кратко:
opt. В качестве параметра - наименование пункта выбора.desc сразу после opt, то можно изменить текст подсказки; если не ставить desc, то в качестве подсказки будет выведена первая фраза ГГ.Info_ClearChoices, то есть «чистит» список пунктов выбора для его последующего наполнения операторами opt.Иногда бывает нужно «сослаться» на уже созданные ранее функции-реакции на пункты выбора. В этом случае делаем так (добавляем следующий код):
person: NONE_102_Peter, 11 Info: EinAndererDialog > Я что вспомнил-то. Про кита и слона. < А, про угадайку. Ну и кто сильней, на этот раз? %IAwaitedYou, optKit %IAwaitedYou, optSlon
Результат:
func void NONE_102_Peter_EinAndererDialog_Info() { AI_Output(other, self, "NONE_102_Peter_EinAndererDialog_Info_15_01"); // Я что вспомнил-то. Про кита и слона. AI_Output(self, other, "NONE_102_Peter_EinAndererDialog_Info_11_02"); // А, про угадайку. Ну и кто сильней, на этот раз? Info_ClearChoices(NONE_102_Peter_EinAndererDialog); Info_AddChoice(NONE_102_Peter_EinAndererDialog, " Кит?", NONE_102_Peter_IAwaitedYou_optKit); Info_AddChoice(NONE_102_Peter_EinAndererDialog, "Слон, наверное?", NONE_102_Peter_IAwaitedYou_optSlon); };
То есть используем оператор »%» с параметрами ИмяПунктаДиалога и наименованиями уже описанных ранее/выше функций реакции.
«Начать» квест совсем-совсем нетрудно :). Простой пример (example13.dia):
person: NONE_103_Anna, 8 Info: StartMis > Что тебе нужно? < Принеси мне Цветочек Аленький, молодец удаленький. $STARTMIS RedFlower $LOGMISS RedFlower, Аленький Цветочек для Анны
Результат:
func void NONE_103_Anna_StartMis_Info() { AI_Output(other, self, "NONE_103_Anna_StartMis_Info_15_01"); // Что тебе нужно? AI_Output(self, other, "NONE_103_Anna_StartMis_Info_08_02"); // Принеси мне Цветочек Аленький, молодец удаленький. MIS_RedFlower = LOG_RUNNING; Log_CreateTopic(TOPIC_RedFlower, LOG_MISSION); Log_SetTopicStatus(TOPIC_RedFlower, LOG_RUNNING); B_LogEntry(TOPIC_RedFlower, "Аленький Цветочек для Анны"); };
Ну, тут все понятно и без обьяснений. :)
За добавление записей в журнал (B_LogEntry) отвечает оператор $LOGMISS.
Результат работы конструкции
$MISFAIL TestQuest
выглядит так:
MIS_TestQuest = LOG_FAILED; Log_SetTopicStatus(TOPIC_TestQuest, LOG_FAILED);
а конструкции
$MISSUCC TestQuest $EXP XP_TestQuest $EXP 100
так:
MIS_TestQuest = LOG_SUCCESS; Log_SetTopicStatus(TOPIC_TestQuest, LOG_SUCCESS); B_GivePlayerXP(XP_TestQuest); B_GivePlayerXP(100);
Примечания в журнале формируем так:
$LOGNOTE CityTraderz, Торговцы в основном обретаются на Торговой площади.
Результат:
Log_CreateTopic(TOPIC_CityTraderz, LOG_NOTE); B_LogEntry(TOPIC_CityTraderz, "Торговцы в основном обретаются на Торговой площади.");
Еще одна интересная фишка - «автоформирование» диалога кражи. Я думаю, тут все понятно без лишних слов. Для мужского персонажа букву W в параметрах оператора info нужно убрать. Вот пример (example14.dia):
person: NONE_103_Anna, 8 Info: Pickpocket w 20 45
Результат:
instance NONE_103_Anna_PICKPOCKET (C_INFO) { npc = NONE_103_Anna; condition = NONE_103_Anna_PICKPOCKET_Condition; information = NONE_103_Anna_PICKPOCKET_Info; important = FALSE; permanent = TRUE; nr = 900; description = Pickpocket_20_Female; }; func int NONE_103_Anna_PICKPOCKET_Condition() { if (C_Beklauen(20, 45)) { return TRUE; }; }; func void NONE_103_Anna_PICKPOCKET_Info() { Info_ClearChoices(NONE_103_Anna_PICKPOCKET); Info_AddChoice(NONE_103_Anna_PICKPOCKET, DIALOG_BACK, NONE_103_Anna_PICKPOCKET_BACK); Info_AddChoice(NONE_103_Anna_PICKPOCKET, DIALOG_PICKPOCKET, NONE_103_Anna_PICKPOCKET_DoIt); }; func void NONE_103_Anna_PICKPOCKET_BACK() { Info_ClearChoices(NONE_103_Anna_PICKPOCKET); }; func void NONE_103_Anna_PICKPOCKET_DoIt() { B_Beklauen(); Info_ClearChoices(NONE_103_Anna_PICKPOCKET); };
Автор статьи - marazmus.