Город Мастеров
IPB

Здравствуйте, гость ( Вход | Регистрация )

 
Ответить в эту темуОткрыть новую тему
> Динамические диалоги, туториал
_kaa_
сообщение Oct 28 2005, 20:13
Сообщение #1


Level 9
Иконки Групп

Класс: Волшебник
Характер: Chaotic Good
Раса: Дракон
NWN: Скриптинг [PW]



Генерация динамических диалогов (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» видим примерно такую картину.

(IMG:http://kaa.mhost.ru/nwn/img/dd-demo2_1.jpg)

Пример 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 подобных, но все же отдельных скрипта.

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

(IMG:http://kaa.mhost.ru/nwn/img/dd-demo1_0.gif)

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

(IMG:http://kaa.mhost.ru/nwn/img/dd-demo1_1.gif)

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

Вот тут можно найти тестовый модуль
Вернуться в начало страницы
Скопировать ник в поле быстрого ответа
+Ответить с цитированием данного сообщения
Vanes
сообщение Oct 28 2005, 23:47
Сообщение #2


Level 11
Иконки Групп

Класс: Вор
Характер: Neutral Evil
Раса: Эльф
NWN: Скриптинг [PW]
Validor2



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

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

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

зы если не совсем понятно о чем я написал - могу выложить пример (только уже утром)...
Вернуться в начало страницы
Скопировать ник в поле быстрого ответа
+Ответить с цитированием данного сообщения
_kaa_
сообщение Oct 29 2005, 08:05
Сообщение #3


Level 9
Иконки Групп

Класс: Волшебник
Характер: Chaotic Good
Раса: Дракон
NWN: Скриптинг [PW]



Попробовать можно, все же реальная польза от сокращения количества скриптов вроде бы есть.
Вернуться в начало страницы
Скопировать ник в поле быстрого ответа
+Ответить с цитированием данного сообщения
Vanes
сообщение Oct 29 2005, 10:43
Сообщение #4


Level 11
Иконки Групп

Класс: Вор
Характер: Neutral Evil
Раса: Эльф
NWN: Скриптинг [PW]
Validor2



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

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_
сообщение Nov 10 2005, 14:50
Сообщение #5


Level 9
Иконки Групп

Класс: Волшебник
Характер: Chaotic Good
Раса: Дракон
NWN: Скриптинг [PW]



Передал dd_ по совету Vanes'а, стало на 10 скриптов меньше.
Обновил модуль с примером, лежит там же (.rar, 21kb)
Кроме того выложил .erf для импорта, лежит тут (.erf, 39kb)

Сообщение отредактировал _kaa_ - Nov 10 2005, 14:51
Вернуться в начало страницы
Скопировать ник в поле быстрого ответа
+Ответить с цитированием данного сообщения

Ответить в эту темуОткрыть новую тему
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0

 



Текстовая версия Сейчас: 28th March 2024 - 18:03