Помощь - Поиск - Пользователи - Календарь
Полная версия: Динамические диалоги
Город Мастеров > РЕДАКТОРЫ > Шарды рунета
_kaa_
Генерация динамических диалогов (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);


где:
sInfo - Отображаемая информация о диалоге
iWinSize - "ширина" окна, т.е. количество одновременно отображаемых пунктов в диалоге. максимум 10, минимум 3, по умолчанию 10.
sAction_Script - имя скрипта, который будет заниматься обработкой результата. скрипту будет доступны значения sDesc,sValue из выбранного пункта списка.
iConfirmation - Регулирует вид диалога, требуется ли подтверждение выбранного элемента или нет.
Если FALSE - при выборе элемента списка сразу вызывается действие ("быстрый" вариант),
если TRUE - вызывается подтверждение выбранного пункта, при желании с выдачей дополнительной информации по этому пункту
sConfirmationScript - если предыдущий параметр TRUE, при отображении подтверждения вызывается указанный скрипт. Результат работы такого скрипта (строка) отображается в виде "подсказки" в диалоге.
iReturnAfterOk - после выбора пункта "ок" вернуться в начало диалога.

добавить пару sDesc,sValue в диалог можно примерно так:
Neverwinter Script Source
dd_Add(oPC,sDesc,sValue);


2. Сам диалог

3. Обработка результата. Указанный в dd_Init() скрипт вызывается и ему доступны переменные sValue,sDesc из выбранного элемента.

Скрипт инициализации всё выглядит примерно так:

dd_Init(oPC,sInfo,sAction_Script...)

dd_Add(oPC,sDesc1,sValue1)
dd_Add(oPC,sDesc2,sValue2)
dd_Add(oPC,sDesc3,sValue3)

dd_Show(oPC);

В тестовом модуле есть скрипты-шаблоны, которые нужно записать в папку /scripttemplates , обязательно с расширением .txt, тогда при создании нового скрипта достаточно будет щелкнуть по нужному, все основные функции там уже есть.

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

Пример 1.

Самый простой. Откроем игроку диалог в котором будут перечислены все объекты на локации. При выборе ничего происходить не будет, это просто демонстрация работы диалога.

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);
}


Создадим на тестовой локации рычаг, ему на onUsed назначим этот скрипт.
Запускаем, при нажатии на рычаг «Пример 1» видим примерно такую картину.

user posted image

Пример 2.

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

Скрипт первый, инициализация и заполнение диалога.

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);

}


Пример 3.

Третий пример с точки зрения dd проще чем второй, но уже делает что-то полезное.
Задача: сделать систему порталов, при использовании портала он активируется и показывает список других, уже активированных порталов. При выборе портала из списка игрок переносится на выбранный портал.
Порталы должны работать и для сингла, и для мультика.
Порталы работают в обе стороны, но при наличии переменных работают а) только как выход б) только как вход в сеть.
Можно добавлять любое количество не пересекающихся между собой портальных «сетей», один тег – одна сеть.
Само собой нужно хранить список уже активированных порталов.

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);
}


Скрипт на этапе написания максимально подробно комментировался, надеюсь будет понятно.

Пока мы говорили только об использовании dd, теперь немного о самой библиотеки «_inc_dd»

Диалоги в НВН пока не умеют возвращать номер выбранного пункта в диалоге, поэтому единственный путь – выставить действие на каждый пункт и оттуда получить номер. Но это приводит к тому, что на 10 пунктов диалоге нужно 20 подобных, но все же отдельных скрипта.

Логическая структура выглядит примерно так:

user posted image

или если смотреть в тулсете то так:

user posted image

Все вызовы из скриптов ведут к функциям из “_inc_dd”, так удобнее их редактировать.
Версия dd на текущий момент – 0.1 альфа, тем не менее можно использовать. Если будут ошибки – я выложу исправления.
Отдельный .erf я не делаю, выкладываю пока только тестовый модуль с примерами, если кому интересно.

Вот тут можно найти тестовый модуль
Vanes
QUOTE
Диалоги в НВН пока не умеют возвращать номер выбранного пункта в диалоге, поэтому единственный путь – выставить действие на каждый пункт и оттуда получить номер. Но это приводит к тому, что на 10 пунктов диалоге нужно 20 подобных, но все же отдельных скрипта.

если я правильно понял, то ты говоришь о 10ти скриптах на событии "Text Appears When.." и 10ти на "Actions Taken"... если нет, то дальше можно не читать, а пост удалить smile.gif

так вот реально же можно это кол-во сократить практически в 2 раза, а именно вместо первых 10ти скриптов использовать всего 1...
для этого используется всего 1 локальная переменная, которая увеличивается при каждом запуске скрипта и тем самым показывает, какой по номеру ответ диалога "пытается отобразиться", а в самом скрипте стоит свитч, который проверяет эту переменную...
однако, даже в таком случае придется использовать разные токены в описании ответов, т.к. они отобразятся в диалоге позже, чем закончится проверка всех "Text Appears When.."...

зы если не совсем понятно о чем я написал - могу выложить пример (только уже утром)...
_kaa_
Попробовать можно, все же реальная польза от сокращения количества скриптов вроде бы есть.
Vanes
в общем вот пример...
немного грубоват, и некоторых вещей нет (например варианта назад/на предыдущую страницу), но все остальное делается по подобию...

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;
}
_kaa_
Передал dd_ по совету Vanes'а, стало на 10 скриптов меньше.
Обновил модуль с примером, лежит там же (.rar, 21kb)
Кроме того выложил .erf для импорта, лежит тут (.erf, 39kb)
Это текстовая версия — только основной контент. Для просмотра полной версии этой страницы, пожалуйста, нажмите сюда.
Invision Power Board © 2001-2024 Invision Power Services, Inc.