Автор | Сообщение |
ZZYZX - UAC Commissar -
| | 6284 |
Doom Rate: 1.65 Posts quality: +1630 |
Отправлено: 21.09.15 04:38:26 | | | Трэд на зандронуме: http://zandronum.com/forum/showthread.php?tid=6433
http://www.mediafire.com/download/srh9kga10dyzeap/zzconv.pk3
Код отдельно здесь: http://pastebin.com/0kcRVmHx (библиотека), http://pastebin.com/RfXDhpgy (использование)
Скриншот:
Собственно, суть.
Я тут недавно глянул на то, как люди пишут диалоговые либы на ACS, и стало мне очень страшно жить.
Поэтому я потратил полтора часа времени и накодил либу, от которой мне жить не страшно. Смею надеяться, что кому-нибудь она действительно будет полезна. Впрочем, пофиг.
Пример использования:#include "zcommon.acs"
#import "zzconv.inc"
script 1 (void) // starting dialogue
{
// don't switch if dialogue has been started already
Conversation_Init("Spawn a Cacodemon?\\n(or a cyberdemon if you have the BFG)\\n(мяу, кстати)\nYes[default,target_named=test(666,41,0)]\nNo[target=11]\nGIMME A CYBERDEMON!!![can_activate=13,target=14]");
}
script "test" (int arg1, int arg2, int arg3) // spawn caco named
{
ACS_ExecuteAlways(10, 0, arg1, arg2, arg3);
}
script 10 (int arg1, int arg2, int arg3) // spawn caco
{
print(i:arg1, c:' ', i:arg2, c:' ', i:arg3); // should output 666 41 0
SpawnSpot("TeleportFog", 1);
SpawnSpotFacing("Cacodemon", 1);
}
script 11 (void)
{
Conversation_Init("Maybe spawn a Pain Elemental then?\nYes[target=12]\nNo[default]");
}
script 12 (void) // spawn pain
{
SpawnSpot("TeleportFog", 1);
SpawnSpotFacing("PainElemental", 1);
}
script 13 (void)
{
SetResultValue(!!CheckInventory("BFG9000"));
}
script 14 (void)
{
SpawnSpot("TeleportFog", 1);
SpawnSpotFacing("Cyberdemon", 1);
}
На практике это выглядит так:
http://i.imgur.com/wrpEh0A.png (у игрока нет бфг, скрипт 13 возвращает 0)
http://i.imgur.com/fYpYbx2.png (у игрока есть бфг, скрипт 13 возвращает 1)
Каждый скрипт должен выполняться от имени игрока, который просматривает диалог. В мультиплеере, соответственно, должно быть CLIENTSIDE. Не проверял, если что, могу допилить.
Одновременно может отображаться только один диалог.
Переключение диалогов возможно либо насильственно (т.е. так: ConvInternal_Active = false; Conversation_Init(...); ), либо в результате нажатия на вариант выбора в диалоге (там первая команда делается автоматически).
Запустить новый диалог при наличии активного другого диалога НЕВОЗМОЖНО. Исключение — запуск диалога из target-скрипта (см. про доступные параметры ниже).
Это сделано из-за того, что даже PROP_TOTALLYFROZEN не фильтрует +use, т.е. если в гуе поменять BT_USE на что-нибудь другое, то это можно и убрать. Но об интерфейсе позже.
А сейчас подробности по синтаксису строки, пихаемой в Conversation_Init.
Выглядит она так:"заголовок диалога\nвариант выбора 1[параметры]\nвариант выбора 2[параметры]"
Параметры можно не писать. Без параметров получается постоянно активный пункт диалога, при выборе которого происходит тупо выход из диалога без последствий.
Если вы пишете параметры, то писать их надо не абы как, а через запятую. Пример выше.
Доступные параметры:
default: помечает вариант выбора как "по умолчанию". Он будет выделен в самом начале. Крайне не рекомендую совмещать can_activate с default.
can_activate, can_activate_named: указывает скрипт, который будет вызываться для проверки, можно ли выбрать определённый вариант. Первый принимает число, второй название. Если не указано, то вариант всегда активен.
target, target_named: указывает скрипт, который будет вызываться при выборе этого варианта. Если указан can_activate, и он возвращает 0, то данный скрипт не выполняется.
visible, visible_named: указывает скрипт, который будет вызываться для проверки видимости этого варианта. Статично (проверяется ровно один раз, при инициализации экрана. В то время как can_activate вызывается каждый тик).
Для каждого скрипта можно указывать аргументы (например: target_named=test(666,41,0), или 12(666) для номерного скрипта). Пример выше.
Пробелов между запятыми, скобками и аргументами НЕ ДОЛЖНО БЫТЬ. В ацс дорог каждый опкод, и вообще мне лень было писать Trim
Аргументы могут быть ТОЛЬКО числами. Fixed не принимается.
Если указать параметры несколько раз (это касается named и не-named вариантов тоже), то будет использован только последний.
Теперь о гуе. Гуй находится в библиотеке скриптом номер 999 c названием zzconv_gui.
Выглядит он как крутящаяся в цикле клиентсайд-фигня, которая отображает текущий диалог (тот, который последний был вызван функцией Conversation_Init), если ConvInternal_Active == true.
Этот же скрипт фризит игрока и обрабатывает его ввод. Но я не знаю, насколько надёжен фриз на клиентской стороне. Это надо тестировать, а сейчас четыре часа ночи. Главное, что как минимум в сингле это работает, да.
Собственно, как выглядит этот скрипт.
Во-первых, он следит за состоянием кнопки use. Если во время открытия диалога была нажата кнопка use, скрипт будет ждать, пока её отожмут, перед тем как предпринимать активные действия. Это связано с тем, что сам диалог часто открывается по кнопке use.
Во-вторых, он следит за переменной ConvInternal_Active. Если она true, значит в данный момент активен какой-то диалог. Игрок фризится и происходит отрисовка диалога. Если же false, то игрок перестаёт фризиться и ничего не отрисовывается.
При отрисовке диалога используется массив ConvInternal_Strings. Массив этот генерируется функцией Conversation_Init, как и всё остальное, что является массивом и начинается с ConvInternal, да.
Ещё при отрисовке диалога используется переменная ConvInternal_Count. Она не является массивом, однако тоже генерируется функцией Conversation_Init. Ибо Джашин воистину!..
Эта переменная (ConvInternal_Count) символизирует собой фактическое количество строк во всех массивах ConvInternal.
Текущий вариант (тот, на котором курсор) хранится в ConvInternal_CurrentItem. Если -1, значит ничего не выбрано. Если больше ConvInternal_Count, то, в принципе, тоже ничего не выбрано, но лучше так не делать.
По дефолту текущий вариант прикольно подсвечивается жёлтым цветом и мигающей хреновиной.
Ах да, и если функция Conversation_CheckActiveChoice(индекс) возвращает false (напоминаю, она вызывает скрипт, который мы указывали в can_activate), то итем будет отображаться серым и его будет невозможно выделить. А даже если и выделится, то не выполнится.
Мышь не реализована, так как сейчас уже пять часов ночи. Точнее, утра. Возможно, позже и по заявкам.
Но вообще её там легко реализовать, т.к. всё общение с игроком происходит в одном месте. Достаточно собственно прикрутить туда мышь.
Мышь реализована. Активируется при попытке пошевелить мышью. Деактивируется при попытке пошевелить клавиатурой.
Кажется, всё. За остальным можно ковырять руками скрипт. И даже нужно.
з.ы. вообще-то, либа была накодена даже не столько потому, что у шадовмена такие страшные скрипты, сколько потому, что я знаю как сделать такую либу, но своих проектов у меня нет, где её можно применить.
Ну и ещё она действительно может быть весьма удобной, если слегка допилить. В основном, например, поддержку нескольких строк в заголовке диалога. Сейчас наблюдается полное отсутствие наличия такой фичи.
Допиливание тоже по заявкам, да. |
|
|
2 |
2 |
1 |
|
|
| |
VladGuardian = Commissar =
| 5537 |
Doom Rate: 1.28 Posts quality: +1934 |
Отправлено: 21.09.15 08:59:14 | | | Кажется опечатка, исправь:
SetResultValue(!!CheckInventory("BFG9000")); (два отрицания подряд) | |
|
4 |
10 |
23 |
|
|
| |
ZZYZX - UAC Commissar -
| | 6284 |
Doom Rate: 1.65 Posts quality: +1630 |
Отправлено: 21.09.15 09:03:51 | | | Это не опечатка, это приведение любого ненулевого значения к 1. | |
|
2 |
2 |
1 |
|
|
| |
VladGuardian = Commissar =
| 5537 |
Doom Rate: 1.28 Posts quality: +1934 |
Отправлено: 21.09.15 10:01:57 | | | ZZYZX: | Это не опечатка, это приведение любого ненулевого значения к 1. |
Воу, буду знать, спасибо! Я понял - это типа "униформизация" логического значения: 0 -> 0 any -> 1 Изящно, никогда бы не догадался сам до такого. | |
|
4 |
10 |
23 |
|
|
| |
Shadowman UAC General
| 8391 |
Doom Rate: 2.08 Posts quality: +1995 |
Отправлено: 21.09.15 11:44:43 | | | Это целых 6 скриптов ушло только на 1 диалог из 3 опций? А сколько тогда уйдет скриптов на сложный диалог с большим количеством текста? Что-то эта библиотека мне совсем не кажется простой... Может, она универсальна, не спорю, но назвать ее простой я не могу. | |
|
1 |
7 |
2 |
|
|
| |
VladGuardian = Commissar =
| 5537 |
Doom Rate: 1.28 Posts quality: +1934 |
Отправлено: 21.09.15 11:57:09 | | | Передачей параметров (числовых значений) в скрипты можно объединить несколько скриптов в один. Что является хорошей практикой в программировании вообще.
Вот эти два прямо просятся:
script 12 (void) // spawn pain
{
SpawnSpot("TeleportFog", 1);
SpawnSpotFacing("PainElemental", 1);
}
script 14 (void) // spawn cyber
{
SpawnSpot("TeleportFog", 1);
SpawnSpotFacing("Cyberdemon", 1);
} Преобразуются в один:
const char* MonsterName[] = {"Zombieman", "Sergeant", "Imp", ...};
script 12 (int monster)
{
SpawnSpot("TeleportFog", 1);
SpawnSpotFacing(MonsterName[ monster ], 1);
} Скрипты 1 и 11 тоже ну ооочень просятся слиться в один. | |
|
4 |
10 |
23 |
|
|
| |
ZZYZX - UAC Commissar -
| | 6284 |
Doom Rate: 1.65 Posts quality: +1630 |
Отправлено: 21.09.15 11:59:02 | | | Shadowman: | Это целых 6 скриптов ушло только на 1 диалог из 3 опций? А сколько тогда уйдет скриптов на сложный диалог с большим количеством текста? |
Я хз, что такое сложный диалог с большим количеством текста. Примерно 12-13 скриптов на такой уйдёт. Ну, может 20. Но это не проблема, т.к. я бы такие вещи раскладывал по разным acs-файлам (scripts/dial_npc1.inc, scripts/dial_npc2.inc, scripts/dialogue.acs = #include), где npc1/npc2 соответственно условные разделители типов неписей, при взаимодействии с которыми будут данные диалоги. И ещё я бы однозначно юзал named-скрипты и окончательно положил конец любому пересечению диалоговых скриптов между собой.
VladGuardian: | Передачей параметров (числовых значений) в скрипты можно объединить несколько скриптов в один. Что является хорошей практикой в программировании вообще. |
Хорошей практикой в программировании является не закидывать в один скрипт абсолютно не связанные друг с другом вещи, как некоторые любят делать (в данном случае не шадовмен). А я регулярно видел, как таким образом абузятся аргументы и в итоге получается нечитаемый атас. Впрочем, почитав твой пример, ты наверное таки прав. Придётся допиливать в синтаксис конфига вещи навроде 12(0), 12(1), 12(2)...
Впрочем, я могу убрать три скрипта и сделать один (script=666), и в качестве аргумента этому скрипту будет передаваться либо 0 (вариант выбран), либо 1 (проверка активности), либо 2 (проверка видимости). Или вообще сделать тупо один скрипт ВООБЩЕ. Который будет вызываться с двумя параметрами. Идентификатор варианта выбора (который можно будет задавать) и причина вызова (аналогично тому что выше). Но тут будет в разы больше возможностей накосячить, как мне кажется. Зато весь отдельно взятый диалог будет управляться одним скриптом. | |
|
2 |
2 |
1 |
|
|
| |
Shadowman UAC General
| 8391 |
Doom Rate: 2.08 Posts quality: +1995 |
Отправлено: 21.09.15 12:06:41 | | | ZZYZX: | Я хз, что такое сложный диалог с большим количеством текста. |
Как пример - те же диалоги Кейна в Тристане. При том, что в них должны быть заранее прописаны все опции, активирующиеся по ходу прохождения игры и выполнения игроком разных квестов, и соблюдены условия, при которых высвечиваются разные ответы. | |
|
1 |
7 |
2 |
|
|
| |
ZZYZX - UAC Commissar -
| | 6284 |
Doom Rate: 1.65 Posts quality: +1630 |
Отправлено: 21.09.15 12:07:56 | | | Shadowman: | При том, что в них должны быть заранее прописаны все опции, активирующиеся по ходу прохождения игры |
Здесь бы это решилось visible-скриптом. А что там в этом скрипте понаписано и как именно он определяет, докуда мы дошли — уже не моя забота. В этом и крутость либы!
Shadowman: | и соблюдены условия, при которых высвечиваются разные ответы. |
Аналогично. Два взаимоисключающих варианта в той же самой проверке visible-скрипта.
Аргументы к скриптам придётся вводить | |
|
2 |
2 |
1 |
|
|
| |
VladGuardian = Commissar =
| 5537 |
Doom Rate: 1.28 Posts quality: +1934 |
Отправлено: 21.09.15 12:30:44 | | | ZZYZX: | Хорошей практикой в программировании является не закидывать в один скрипт абсолютно не связанные друг с другом вещи, как некоторые любят делать (в данном случае не шадовмен). |
Совершенно согласен. У меня наблюдается другая тенденция делать более "универсальные" методы (внутри классов), а также скрипты "помощнее" (передачей двух параметров в скрипт, например, меня не удивишь) Что действительно приводит к заметному ухудшению читабельности, и "размытому" коду, по внешнему виду которого не сразу догадаешься, что именно он делает Так что надо наверное останавливаться на какой-то золотой середине.
Но тем не менее, стараться немножко сократить количество скриптов. Например, ничто не мешает объединить скрипты отвечающие за включение и выключение одной и той же лампочки в комнате. Вот конкретный работающий пример:
script "RoomLightSwitch" (int sector) //---вкл/выкл света в комнатах
{
if (GetSectorLightLevel(sector) == ROOM_LIGHT_OFF)
Light_Fade(sector,ROOM_LIGHT_ON,9);
else
Light_Fade(sector,ROOM_LIGHT_OFF,9);
AmbientSound("Swtch1",127);
} P.S. Извиняюсь за программистский оффтоп. | |
|
4 |
10 |
23 |
|
|
| |
ZZYZX - UAC Commissar -
| | 6284 |
Doom Rate: 1.65 Posts quality: +1630 |
Отправлено: 21.09.15 15:32:15 | | | Да это не программистский оффтоп. Во-первых, слова оффтоп нет. А во-вторых, сама эта тема про программирование! И в разделе о скриптинге (в том числе). В общем я чуть позже сделаю просто передачу статических аргументов в скрипт, таким способом: target_named=dial1_option1(0),can_activate_named=dial1_option1(1). И дальше уже люди смогут писать в меру испорченности, а универсальность только возрастёт. | |
|
2 |
2 |
1 |
|
|
| |
ZZYZX - UAC Commissar -
| | 6284 |
Doom Rate: 1.65 Posts quality: +1630 |
Отправлено: 26.09.15 23:34:38 | | | Добавил передачу аргументов. Добавил описание в первый пост.
Кстати, код благополучно не пашет в зандронуме. Завёл тикет. https://zandronum.com/tracker/view.php?id=2472
Код благополучно не пашет в зандронуме и старой гоззе (1.8.6) после добавления аргументов к скриптам.
Код благополучно пашет в зандронуме и старой гоззе. Минимальное требование — версия 2.7.0.
Добавлено спустя 1 день 17 часов 4 минуты 16 секунд:
Допилил мыш! В оффлайне работает хорошо, в онлайне — умеренно адекватно. | |
|
2 |
2 |
1 |
|
|
| |
UsernameAK - Lance Corporal -
| 150 |
Doom Rate: 1.79 Posts quality: +13 |
Отправлено: 05.11.15 10:57:11 | | | Spawn a Cacodemon?
(or a cyberdemon if you have the BFG)
(мяу, кстати)
Yes[default,summon=Cacodemon,summonfog=TeleportFog]
No[goto=2]
GIMME A CYBERDEMON!!![if_has_class=BFG9000,summon=Cyberdemon,summonfog=TeleportFog]
Maybe spawn a Pain Elemental then?
Yes[summon=PainElemental,summonfog=TeleportFog]
No[default]
вполне можно сделать так, еще и в отдельном файле, но будет работать на глуме | |
|
| |
ZZYZX - UAC Commissar -
| | 6284 |
Doom Rate: 1.65 Posts quality: +1630 |
Отправлено: 05.11.15 19:06:39 | | | Ты забыл кучу параметров кроме summon=... В частности тэг куда суммонить. А ведь суммонить ещё можно и по координатам.
USDF не просто так придумали. И подобные системы. Но моя именно что проще. | |
|
2 |
2 |
1 |
|
|
| |
UsernameAK - Lance Corporal -
| 150 |
Doom Rate: 1.79 Posts quality: +13 |
Отправлено: 06.11.15 14:27:32 | | | ZZYZX: | Но моя именно что проще. |
она была бы проще если бы все можно было бы слить в один скрипт
Добавлено спустя 13 минут 6 секунд:
в 4 скрипта вжал
#include "zcommon.acs"
#import "zzconv.inc"
script 1 (void) // starting dialogue
{
// don't switch if dialogue has been started already
Conversation_Init("Spawn a Cacodemon?\\n(or a cyberdemon if you have the BFG)\\n(мяу, кстати)\nYes[default,target_named=ConversationSpawn(19)]\nNo[target=3]\nGIMME A CYBERDEMON!!![can_activate=2,target_named=ConversationSpawn(114)]");
}
script "ConversationSpawn" (int monster)
{
Thing_SpawnFacing(1, monster, false, 0);
}
script 2 (void) {
SetResultValue(!!CheckInventory("BFG9000"));
}
script 3 (void) {
Conversation_Init("Maybe spawn a Pain Elemental then?\nYes[target_named=ConversationSpawn(115)]\nNo[default]");
} | |
|
| |
ZZYZX - UAC Commissar -
| | 6284 |
Doom Rate: 1.65 Posts quality: +1630 |
Отправлено: 06.11.15 15:02:52 | | | UsernameAK: | она была бы проще если бы все можно было бы слить в один скрипт |
Можешь слить. Нахрена??? Это усложнение. | |
|
2 |
2 |
1 |
|
|
| |