Генерация динамических диалогов (dd).
Для чего нужно:
Есть довольно частая задача. Отобразить некий список, жестко не заданный и возможно генерируемый скриптом, в диалоге. Дождаться ответа игрока и выполнить некоторое действие.
Поскольку список может быть большой - отобразить только несколько пунктов
и обеспечить "навигацию" клавишами "Следующая страница, Предыдущая страница".
Использование dd можно условно разделить на три этапа:
1. Скрипт: Инициализация, заполнение списка (из чего будем выбирать)
2. Взаимодействие с игроком: Сам диалог, выбор элемента из списка.
3. Скрипт: Реакция на выбранный пункт, обработка.
1.Список задается парами значений типа string. {sDesc,sValue}.
sDesc - описание (та часть, что будет видна в диалоге)
sValue - значение (для универсальности строка)
Но перед заполнением списка требуется инициализация dd
В диалоге отразится значение sDesc, а при выборе этого пункта
указанному скрипту обработки будет доступно значение sValue.
Параметры (скрипт обработки, "ширина" окна и т.п.) задаются
примерно так:
Neverwinter Script Source |
dd_Init(object oPC, string sInfo, string sAction_Script,int iWinSize=10,int iConfirmation=1,string sConfirmationScript="",int iReturnAfterOk=1); |
Neverwinter Script Source |
dd_Add(oPC,sDesc,sValue); |
Neverwinter Script Source |
//:://///////////////////////////////////////////// //:: Name Пример 1 //:: FileName dd_ex_showobj.nss //:: Copyright © //::////////////////////////////////////////////// /* Шаблон для инициализации dd */ //::////////////////////////////////////////////// //:: Created By: _kaa_ //:: Created On: 27/10/2005 //::////////////////////////////////////////////// #include "_inc_dd" void main() { //игрок, что нажал на рычаг. object oPC = GetLastUsedBy(); //Имя скрипта обработки задавать не будем. if (!dd_Init(oPC,"Пример 1: Список всех объектов на локации","",10,FALSE)) { //произошла ошибка инициализации (к примеру oPC - не игрок), выходим return; } object oObj = GetFirstObjectInArea(GetArea(oPC)); while (GetIsObjectValid(oObj)) { //в описание добавим имя объекта и его тег, значение будет пустым // - мы не собираемся обрабатывать выбранный элемент в этом примере. dd_Add(oPC,GetName(oObj)+"["+GetTag(oObj)+"]","-"); oObj = GetNextObjectInArea(GetArea(oPC)); } //запустить диалог. dd_Show(oPC); } |
Neverwinter Script Source |
//:://///////////////////////////////////////////// //:: Name dd_ex_inv_onuse //:: FileName dd_ex_inv_onuse.nss //:: Copyright © 2005 by _kaa_ //::////////////////////////////////////////////// /* Пример использования dd. Позволяет просмотреть все предметы в инвентаре (в примере - инвентаря вызывающего) и сделать с выбранным какое-либо действие (обработка в скрипте dd_ex_inv_run */ //::////////////////////////////////////////////// //:: Created By: _kaa_ //:: Created On: 27/10/2005 //::////////////////////////////////////////////// #include "_inc_dd" void main() { object oPC = GetLastUsedBy(); dd_Init(oPC,"Пример 2. Инвентарь","dd_ex_inv_run"); object oItem =GetFirstItemInInventory(oPC); while (GetIsObjectValid(oItem)) { dd_Add(oPC,GetName(oItem)+"["+GetTag(oItem)+"]",GetTag(oItem)); oItem =GetNextItemInInventory(oPC); } dd_Show(oPC); } |
Neverwinter Script Source |
//:://///////////////////////////////////////////// //:: Name dd_ex_inv_run //:: FileName dd_ex_inv_run.nss //:: Copyright © 2004-2005 by _kaa_ //::////////////////////////////////////////////// /* скрипт обработки инвентаря. тег выбранного элемента - string sTag = dd_GetLastValue(oPC); */ //::////////////////////////////////////////////// //:: Created By: _kaa_ //:: Created On: 27/10/2005 //::////////////////////////////////////////////// #include "_inc_dd" void main() { object oPC=OBJECT_SELF; string sTag = dd_GetLastValue(oPC); object oItem = GetItemPossessedBy(oPC, sTag); DestroyObject(oItem); } |
Neverwinter Script Source |
//:://///////////////////////////////////////////// //:: Name dd_ex_port_onuse //:: FileName dd_ex_port_onuse.nss //:: Copyright © 2004-2005 by _kaa_ //::////////////////////////////////////////////// /* Пример использования dd Система порталов. При использовании показывает список всех активных порталов, при выборе переносит игрока к выбранному порталу. Добавлено: усложним пример, сделаем систему порталов а-ля диабло, т.е. требующих предварительной активации, только потом будет доступен. */ //::////////////////////////////////////////////// //:: Created By: _kaa_ //:: Created On: //2005 //::////////////////////////////////////////////// //подключаем библиотеку с dd и функциями location -> string, string -> location #include "_inc_dd" #include "_inc_loc2str" const int DD_EX_SINGLE_MODE = 0; //1 - если сингл, 0 - мультик const string DD_EX_MULTI_TAG = "dd_ex_portal"; //если мультик - ресреф и тег предмета, где будем хранить данные const string DD_EX_COUNT = "dd_ex_count"; //имя переменной, где будем хранить количество уже запомненных порталов const string DD_EX_PORTAL = "dd_ex_portal"; //префикс сохраненных переменных с локацией запомненных порталов //полное имя переменной будет DD_EX_PORTAL+DD_EX_ //получим объект, на который будем записывать локальные переменные //для сингла вещь из инвентаря не подойдет, локальные переменные там не сохраняются, //поэтому для сингла пишем на игрока. object ex_GetObjectForSave(object oPC) { //если сингл - вернем игрока и на нем будем писать переменные if (DD_EX_SINGLE_MODE) return oPC; //если мультик - найдем вещь в инвентаре у игрока с тегом MULTI_TAG object oItem = GetItemPossessedBy(oPC,DD_EX_MULTI_TAG); if (GetIsObjectValid(oItem)) return oItem; //нету еще такой вещи - создадим oItem = CreateItemOnObject(DD_EX_MULTI_TAG,oPC); if (GetIsObjectValid(oItem)) return oItem; //кривой ресреф, нельзя создать вещь - ошибка. return OBJECT_INVALID; } //проверим, активен ли уже данный портал для игрока. если нет - сохраним void ex_IsAlreadyChecked(object oPC, object oPortal) { object oStore = ex_GetObjectForSave(oPC); //проверим, есть ли у нас объект для записи переменных if (!GetIsObjectValid(oStore)) { //ошибка, сообщим и выходим WriteTimestampedLogEntry("Error! Can't get object for store."); return; } //убедимся, что oPortal - действительно объект. if (!GetIsObjectValid(oPortal)) { //ошибка, сообщим и выходим WriteTimestampedLogEntry("Error! oPortal is not valid object."); return; } //получим локацию портала в виде строки. string sLoc = LocationToString(GetLocation(oPortal)); //с объекта oStore узнаем, сколько уже на нем есть записей. int n = GetLocalInt(oStore,DD_EX_COUNT); int i; if (n) for (i=0; i <=n; i++) { if (sLoc == GetLocalString(oStore,DD_EX_PORTAL+IntToString(i))) //нашли, выходим return; } //не нашли, надо запомнить и увеличить счетчик n++; SetLocalString(oStore,DD_EX_PORTAL+IntToString(n),sLoc); SetLocalInt(oStore,DD_EX_COUNT,n); //надо бы сюда добавить какой-нибудь визуальный эффект при добавлении. //для сингла пойдет анимация портала, для мультика - любой виз. эффект на игрока //а пока просто напишем игроку, что портал активирован FloatingTextStringOnCreature("Портал "+GetName(GetArea(oPortal))+"."+GetName(oPortal)+" активирован",oPC,TRUE); } void main() { //Кто кликнул на портал object oLastUsed = GetLastUsedBy(); //точно игрок кликнул на портал? if (!GetIsPC(oLastUsed)) return; //если игрок еще в битве - сразу выходим, диалог все равно не запустится. //можно добавить вывод предупреждения. if (GetIsInCombat(oLastUsed)) return; //инициализируем dd. для игрока oLastUsed() //приветствие диалога - "Порталы" //скрипт обработки - "dd_ex_port_jump" //размер "окна" - 10 (максимальное количество пунктов в диалоге, от 3 до 10 //используем вариант без второго уровня диалогов, т.е. без подтверждения if (!dd_Init(oLastUsed,"Порталы","dd_ex_port_jump",10,FALSE)) { //произошла ошибка инициализации, выходим return; } /* //1. "простой" вариант, просто перебирает все плейсиблы-порталы. //найдем все порталы (все плейсиблы с тегом"dd_ex_portal") int n = 0; object oPortal = GetObjectByTag("dd_ex_portal",0); while (GetIsObjectValid(oPortal)) { n++; if (oPortal != OBJECT_SELF && GetObjectType(oPortal) == OBJECT_TYPE_PLACEABLE) //и добавим пару (описание, значение) в диалог. //описание - название локации + имя плейсибла //значение - текстовое представление типа location, которое //не меняется при добавление\удаленние но зависит //от уникальности тегов локаций, если будут одинаковые теги //локации - телепорты будут глючить. dd_Add(oLastUsed,GetName(GetArea(oPortal))+"."+GetName(oPortal),LocationToString(GetLocation(oPortal))); oPortal = GetObjectByTag("dd_ex_portal",n); } */ /*2. Чуть более сложный вариант, а-ля диабло. Портал требует активации, только потом доступен удаление уже активного портала из списка не планируется делать, если "приспичило" - просто удалите предмет, на который "сохраняются" переменные (для мультика) */ //проверим, запомнен ли уже этот портал? если нет - запомним. ex_IsAlreadyChecked(oLastUsed,OBJECT_SELF); //получим объект, на котором хранятся переменные object oStore = ex_GetObjectForSave(oLastUsed); //проверим, действительно ли у нас есть объект для записи переменных if (!GetIsObjectValid(oStore)) { //ошибка, сообщим и выходим WriteTimestampedLogEntry("Error! Can't get object for store."); return; } //с объекта oStore узнаем, сколько уже на нем есть записей. //каждую запись добавим в диалог int n = GetLocalInt(oStore,DD_EX_COUNT); object oPortal; string sLoc; int i; for (i=1; i <=n; i++) { //читаем по очереди все сохраненные локации порталов. sLoc = GetLocalString(oStore,DD_EX_PORTAL+IntToString(i)); //найдем объект (ближайший плейсибл к сохранненой локации) oPortal = GetNearestObjectToLocation(OBJECT_TYPE_PLACEABLE,StringToLocation(sLoc)); //и проверим, действительно ли это портал? if (!GetIsObjectValid(oPortal)) { //ошибка, сообщим и выходим WriteTimestampedLogEntry("Error! Wrong saved location :"+sLoc); return; } /*в этом месте можно добавить дополнительную проверку типа и тега объекта oPortal, например если вы хотите сделать более чем одну "сеть" порталов. в примере все порталы имеют один тег, "dd_ex_portal". если мы добавим проверку и будем требовать, чтобы тег портала, на котором кликнули и тег "удаленного" портала совпадали, мы можем разбить порталы на непересекающиеся "сети" порталов: //для этого нам нужно будет создать "синьку" (т.е. новый тип объектов в палитре модуля) нового типа порталов и сменить ему тег. */ if (GetTag(OBJECT_SELF) != GetTag(oPortal)) continue; /*проверим, если на портале есть переменная only_exit - значит можно на него прыгать, но не с него.чтобы не путать игроков - смените внешний вид плейсибла или каким-то другим способом дайте понять что он не работает */ if (GetLocalInt(OBJECT_SELF,"only_exit")) { FloatingTextStringOnCreature("Портал работает только в одну сторону",oLastUsed); return; } /* проверим, если на портале есть переменная only_enter - значит можно c него прыгать, но не на него (т.е. в списке доступных порталов его не будет, хотя с него можно прыгнуть на другие) чтобы не путать игроков - смените внешний вид плейсибла или каким-то другим способом дайте понять что он не работает */ if (GetLocalInt(oPortal,"only_enter")) continue; //если не нужно выводить в диалог тот портал, по которому кликнули - добавим if (OBJECT_SELF != oPortal) //если все верно - запишем название локации и самого портала в диалог dd_Add(oLastUsed,GetName(GetArea(oPortal))+"."+GetName(oPortal),sLoc); } /* посколько диалог идет "сам с собой", нужно пердусмотреть его закрытие. иначе "умные" игроки пойдут воевать с открытым диалогом, чтобы можно было "сбежать" из боя. поэтому запомним в переменной на игроке объект- портал и при запуске "action" проверим расстояние до него / */ SetLocalObject(oLastUsed,"dd_ex_port_obj",OBJECT_SELF); //запустить диалог. dd_Show(oLastUsed); } |
QUOTE |
Диалоги в НВН пока не умеют возвращать номер выбранного пункта в диалоге, поэтому единственный путь – выставить действие на каждый пункт и оттуда получить номер. Но это приводит к тому, что на 10 пунктов диалоге нужно 20 подобных, но все же отдельных скрипта. |
Попробовать можно, все же реальная польза от сокращения количества скриптов вроде бы есть.
в общем вот пример...
немного грубоват, и некоторых вещей нет (например варианта назад/на предыдущую страницу), но все остальное делается по подобию...
Neverwinter Script Source |
int StartingConditional() { /* та самая переменная, которая является счетчиком кол-ва запусков скрипта на "Text Appears When.."*/ int nStartCondit = GetLocalInt(OBJECT_SELF, "nStartCondit"); /* переменная, которая является счетчиком выданных данных, в данном случае гильд, список которых хранится в БД. nStartCondit использовать нельзя, т.к. она будет сбрасываться с каждой новой "страницей" (см. ниже)*/ int nGuildCondit = GetLocalInt(OBJECT_SELF, "nGuildCondit"); /*максимум для nGuildCondit */ int nGuildCount = GetLocalInt(OBJECT_SELF, "nGuildCount"); switch(nStartCondit){ /* 0-я строка, отвечает за заголовок (или хз как он там называется, в общем тот, что в диалогах красным шрифтом пишется обычно)... в данном случае никакой нагрузки не несет, но можно, например, сделать отдельный токен и выводить номер "страницы"*/ case 0:{ // title SetLocalInt(OBJECT_SELF, "nStartCondit", nStartCondit+1); return TRUE; } /* 1-я строка, т.е. первый сверху вариант ответа в диалоге*/ case 1:{ // line1 nStartCondit++; nGuildCondit++; SetLocalInt(OBJECT_SELF, "nStartCondit", nStartCondit); SetLocalInt(OBJECT_SELF, "nGuildCondit", nGuildCondit); /*проверка отвечает за случаи, когда счетчик гильд стал больше, чем их реально кол-во... т.е. если заготовок под варианты 10, а гильд всего 7, то будут выведены 7 вариантов, а не 10, из которых 3 последних пустые*/ if(nGuildCondit>nGuildCount){ return FALSE; } string sName = GetCampaignString("GUILDS", "guild_"+IntToString(nGuildCondit)); /*для каждого варианта свой токен!!!*/ SetCustomToken(301, "["+sName+"]"); /*позволяет отследить на какой именно "странице" был выбран ответ... в принципе в выполняющем скрипте ("Actions Taken") достаточно знать nGuildCondit и порядковый номер выбранного ответа, но мне удобней было его сразу вписывать в нужную переменную*/ SetLocalInt(OBJECT_SELF, "nLine1", nGuildCondit); return TRUE; } /*тоже, что и в case 1:, кроме сециально выделенных строк*/ case 2:{ // line2 /*... ...*/ /*для каждого варианта свой токен!!!*/ SetCustomToken(302, "["+sName+"]"); SetLocalInt(OBJECT_SELF, "nLine2", nGuildCondit); return TRUE; } case 3:{// line 3 /*... ...*/ /*для каждого варианта свой токен!!!*/ SetCustomToken(303, "["+sName+"]"); SetLocalInt(OBJECT_SELF, "nLine3", nGuildCondit); return TRUE; } case 4:{ // line4 /*... ...*/ /*для каждого варианта свой токен!!!*/ SetCustomToken(304, "["+sName+"]"); SetLocalInt(OBJECT_SELF, "nLine4", nGuildCondit); return TRUE; } case 5:{ // line 5 /*... ...*/ /*для каждого варианта свой токен!!!*/ SetCustomToken(305, "["+sName+"]"); SetLocalInt(OBJECT_SELF, "nLine5", nGuildCondit); return TRUE; } /*6-я строка отвечает за ответ "на уровень выше/ в начало диалога"... в моем примере она отображается только в случае, если список гильд закончился, а можно его вообще из этого скрипта убрать, но я оставил для дальнейших возможных потребностей*/ case 6:{ // end SetLocalInt(OBJECT_SELF, "nStartCondit", nStartCondit+1); if(nGuildCondit>nGuildCount){ return TRUE; } return FALSE; } /*7-я строка соответствует варианту ответа "дальше / на след. страницу"... как видно nStartCondit ставится в 0 - именно поэтому нужен был альтернативный счетчик*/ case 7:{ // new page SetLocalInt(OBJECT_SELF, "nStartCondit", 0); if(nGuildCondit>nGuildCount){ return FALSE; } return TRUE; } } return FALSE; } |
Передал dd_ по совету Vanes'а, стало на 10 скриптов меньше.
Обновил модуль с примером, лежит http://kaa.mhost.ru/nwn/dd.rar (.rar, 21kb)
Кроме того выложил .erf для импорта, лежит http://kaa.mhost.ru/nwn/dd.erf (.erf, 39kb)
Русская версия Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)