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

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

Профиль
Фотография
Рейтинг
 
Опции
Опции
Персональное Соглашение
_kaa_ не имеет договора в данный момент.
Персональная информация
_kaa_
Level 9
46 лет
Пол не выбран
moscow
Родились Дек-28-1977
Интересы
Нет данных
Другая информация
Класс: Волшебник
Характер: Chaotic Good
Раса: Дракон
Мастер: NWN: Скриптинг [PW]
Мастер: Нет данных
Мастер: Нет данных
Проект N1: Нет данных
Проект N2: Нет данных
Статистика
Присоединился: 27.04.2004
Просмотры профиля: 27,283*
Последний раз замечен: 2nd July 2009 - 10:34
Местное время: Mar 19 2024, 08:44
437 сообщения (0.06 сообщений в день)
Контактная информация
AIM Нет данных
Yahoo Нет данных
ICQ 7789197
MSN Нет данных
Контакт Скрыт
* Просмотры профиля обновляются каждый час

_kaa_

Master


Темы
Сообщения
Файлы
Комментарии
Друзья
Содержание
28 Oct 2005
Генерация динамических диалогов (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 я не делаю, выкладываю пока только тестовый модуль с примерами, если кому интересно.

Вот тут можно найти тестовый модуль
25 Oct 2005
Кстати, давно пора собрать один-два стандартных хака, чтобы шардостроители могли их использовать и юзерам не приходилось качать одно и то же в разных вариантах\названиях.
Скажем выпустить хак с несколькими более-менее качественными тайлсетами (горы к примеру и т.п., т.е. более-менее стандартные наборы). По объему это достаточно заметно (плюс пользователям), и "вреда" девелоперами вроде нет большого - наложений между хаками с тайлсетами (?вроде бы?) нет.
Даешь русский CEP, только более продуманный. Не нужно кучу попавшего под руку заталкивать в один громадный хак, но "качественные" расширения, часто используемые, давно пора собрать.
Часть 1.

Журналы
Краткая статья о журналах.
Что это такое, для чего, как использовать и немного про динамические журналы.

Начнем с примеров. Отрываем тулсет, заходим в редактор журнала (tools->Journal Editor , или жмем CTRL-ALT-J)

(IMG:http://kaa.mhost.ru/nwn/img/shot1-1.gif)
рисунок 1.1

Структура журнала – двухуровневая. Первый уровень – название записи, т.е. то что будет видно в игре когда запись не раскрыта, так сказать заголовок конкретного под-журнала.
Второй уровень добавляется к указанной записи, обычное использование – шаги выполнения квеста.
\(корень журнала)
+запись 1 – квест (уровень 1)
+квест 1, шаг 1 (уровень 2)
+квест 1, шаг 2 (уровень 1)

+ запись 2, (уровень 1)


Давайте для простоты договоримся о том, как что будем называть.
Есть журнал, он один. Есть редактор журнала, тоже один.
Есть множество записей в журнале – так и буду их называть, запись журнала или сокращенно просто запись. Синонимы – поджурнал, квест.
Для каждой записи можно задать сколько угодно шагов, каждый будет иметь свой номер, название и возможно статус завершения квеста.

Пока вернемся к рисунку 1.1, разберем все что там есть.

Category: Тут описываются основные параметры записи.
Name: Название записи, отображается в игре
Tag Тег записи, используется для управления из диалога/из скрипта . Тег надо делать уникальным в пределах модуля.
Priority Приоритет записи. Если будут активны более одной записи, можно менять порядок отображения квестов в игре, задавая больший приоритет записи (квесту).
XP Тут можно задать количество опыта, получаемого за квест. Это значение можно получить из скрипта, используя функцию GetJournalQuestExperience(string sTag), где sTag – тег нужной записи.

(IMG:http://kaa.mhost.ru/nwn/img/shot1-2.gif)
рисунок 1.2

Добавляем шаги квеста. Каждый шаг имеет свое название (поле text), которое будет показываться игроку, когда он раскроет запись в журнале, ID – обычно номер шага и флажок – является ли этот шаг завершающим. Если достигнут этот шаг – запись переходит из категории «активные квесты» в категорию «выполненные квесты» в журнале игрока в игре.

Собственно на этом вся работа по журналу и заканчивается, дальше посмотрим как можно управлять журналом из диалогов и из скриптов.

Напомню про «цвета», как их можно использовать. НВН понимает теги цветов, т.е. можно кусок текста покрасить в нужный цвет. Делается это примерно так:
<cRGB>text</c>
т.е. парным тегом <c> задается область текста, которая будет окрашена в цвет RGB. Вся сложность в задании параметров. Каждый символ имеет свой код, его числовое значение и используется в качестве параметра.

(IMG:http://kaa.mhost.ru/nwn/img/shot1-3.gif)
рисунок 1.3

Не любой символ можно задать напрямую через тулсет, но выбрать «близко» к 0 или к 255 можно, обычно использую символ “ ” (код 32) и русскую “ю” (код 224).
Т.е. если мы хотим выбрать красный цвет – задаем <cю >text</c>. Полученный RGB цвет будет иметь значение R:224,G:32,B:32, что довольно близко к красному цвету.
Для «продвинутых»: Если есть желание – можно сделать библиотеку и в ней после компиляции попробовать поменять цвета на близкие к 0 и к 255


Добавлено в 20:12
Часть 2.

Как редактировать журнал – разобрались, посмотрим как ими управлять. Т.е. зачем вообще нам нужны записи в журнале.
Для примера создадим примерно такую запись в журнале:

(IMG:http://kaa.mhost.ru/nwn/img/shot2-1.gif)
рисунок 2.1

Создадим простой диалог, в котором можно будет получить наш «квест»
Открываем редактор диалогов (tools->Converstion Editor, или CTRL-ALT-V)
Создаем простой диалог, используя кнопку “add”-добавить.

(IMG:http://kaa.mhost.ru/nwn/img/shot2-2.gif)
рисунок 2.2

В ответе игрока «Да» переходим на закладку «Other Action» (Другие действия) и в поле Journal выбираем нашу запись и начальный шаг квеста.

Теперь, если игрок поговорит с НПС, которому назначен этот диалог и ответит «Да» - у него в журнале появится новая запись, текст которой будет содержать информацию из шага номер 1 (именно его мы задали в диалоге)

Если вы еще не знаете, что такое «скрипты» - дальше можно не читать, вся работа с журналом требует как минимум умения работы с помощником.

Посмотрим, как можно организовать проверку условий выполнения «квеста», проверить выполнение «шагов» и перейти на следующий шаг.

Пусть у нас все тот-же НПС в том же диалоге должен
1) выдать квест,
2) проверить выполнение «шага 1».,
3) выдать задание «шаг 2» и так далее до конца.

Основная «трудность» при работе с журналом – нельзя получить из скриптов\диалогов номер активного в данный момент шага. Т.е. нам приходится помнить это самим.

Давайте заведем локальную переменную для игрока, которая будет отображать, какой сейчас шаг активен. (расмотрим вариант для сингл-модуля, с шардами чуть сложнее из-за необходимости идентифицирования игрока)

Вернемся к нашему диалогу (рисунок 2.2). Перейдем на закладку “Action Taken” (Пункт был выбран), т.е. какой скрипт запустить в случае, если игрок выбрал данный пункт диалога.
Пишем имя скрипт, пусть будет «my_q001_taken01» (my_ - преффикс, чтобы не пересечься случайно со скриптами bioware или любыми другими, q001- номер квеста, taken01 –номер «взятого» шага. Уже по имени нам будет понятно, что примерно скрипт делает.

Жмем кнопку редактировать, тулсет скажет нам что такого скрипта нет, жмем ОК и пишем скрипт.
Neverwinter Script Source
void main()
{
    SetLocalInt(GetModule(),"my_q001_step",1);
}


Скрипт просто устанавливает «глобальную» переменную, т.е. переменную модуля, по имени «my_q001_step» равным 1. Теперь из других скриптов мы можем узнать, какой шаг активен в квесте.


Жмем F7 – компилировать и сохранить скрипт, если ошибок нет – выходим из редактора скриптов.


Давайте добавим в диалог новую запись, ответ НПС в случае если шаг 1 выбран. И заодно уберем из диалога фразу о получении квеста, если квест уже был взят.
Для этого нужно написать скрипт на закладке Text Appears When (Текст появится при условии). Сначала для уже существующей записи, проверим что квест еще не получен и только в этом случае выводим фразу, предлагающую взять квест.
Скрипты с условием выглядят чуть по другому, даже если мы начинаем новый скрипт – сразу формируется шаблон скрипта. Дело в том, что такой скрипт должен возвращать число, если это будет 0 (FALSE, ложь) – фраза в диалоге не появится, 1 – появится.
Назовем скрипт «my_q001_when01», т.е. условие появления шага 01 для квеста номер 001.
Neverwinter Script Source
int StartingConditional()
{
    int iResult;

    iResult = GetLocalInt(GetModule(),"my_q001_step") == 0;
    return iResult;
}


В скрипте мы проверяем переменную, в которой хранится (или еще только будет храниться) номер шага. Если там храниться 0 (в нашем случае там будет 0 до ответа «Да» в диалоге), то возвращается 1 (т.е. верно условие «номер_шага равен 0»).

После того как мы получи квест и попытаемся снова поговорить с НПС – ничего не произойдет, НПС выдаст приветствие и больше ничего. Чего мы пока и добивались.
Теперь добавим в диалог вторую фразу, которая появится в случае, если шаг 1 уже был взят. Там же по идее должна быть проверка на то, что условия квеста для завершения шага 1 выполнены, но у нас пока нет такой цели.

(IMG:http://kaa.mhost.ru/nwn/img/shot2-3.gif)
рисунок 2.3

Скрипты для проверки условий появления фразы и после ответа «Да» отличаются только одной цифрой, номером шага который они проверяют.

Вот и все, закончили самое простое.
В принципе такие простые квесты проще строить Plot Wizard’ом, он позволяет основные простые типы квестов (убить, убить и принести докозательство, найти и принести) сконструировать прямо в нем, просто выбирая нужных НПС, вещи или создавая новых (прям в нем). Правда полученный в результате код будет не оптимальным и вряд ли подойдет для шардов.

Добавлено в 20:13
Часть 3. Custom tokens. (Метки пользователя)


Что такое custom tokens и для чего они используются.
Метки {Метка – token (маркер, токен)} используют тот же механизм, что и раскраска цвета. В текст вставляется тег вида
<CUSTOMXXXX>, где XXXX – просто номер. Номера с 0 до 9 используются bioware в своих скриптах, их использовать нельзя. На место метки при выводе информации подставляется значение метки, которое задается с помощью скриптов функцией
Neverwinter Script Source
void SetCustomToken(
    int nCustomTokenNumber,     // номер метки
    string sTokenValue          // текст
);


Самый простой пример использования в диалогах. При обращении НПС к игроку можно менять часть фразы, в зависимости от пола игрока, т.е. он\она, лорд/леди и т.п., включая окончания. Вот к примеру скрипт Олвина, задающий 20 возможных меток в зависимости от пола персонажа, добавляется в условие Text Appears When (хотя можно куда угодно, до вывода токена)
Neverwinter Script Source
int StartingConditional()
{
    object oPC = GetPCSpeaker();
    if (GetGender(oPC)==GENDER_MALE)
    {
        SetCustomToken(2001, "он");
        SetCustomToken(2002, "его");
        SetCustomToken(2003, "ему");
        SetCustomToken(2004, "друг");
        SetCustomToken(2005, "мужчина");
        SetCustomToken(2006, "мой");
        SetCustomToken(2007, "Ясень копий");
        SetCustomToken(2008, "ся");
        SetCustomToken(2009, "");
        SetCustomToken(2010, "Хозяин");
        SetCustomToken(2011, "пришел");
        SetCustomToken(2012, "ел");
        SetCustomToken(2013, "мог");
        SetCustomToken(2014, "приятель");
        SetCustomToken(2015, "гость");
        SetCustomToken(2016, "ый");
        SetCustomToken(2017, "");
        SetCustomToken(2018, "ец");
        SetCustomToken(2019, "господин");
        SetCustomToken(2020, "человек");
    }
    else
    {
        SetCustomToken(2001, "она");
        SetCustomToken(2002, "ее");
        SetCustomToken(2003, "ей");
        SetCustomToken(2004, "подруга");
        SetCustomToken(2005, "женщина");
        SetCustomToken(2006, "моя");
        SetCustomToken(2007, "Фрейя обручий");
        SetCustomToken(2008, "ась");
        SetCustomToken(2009, "а");
        SetCustomToken(2010, "Хозяйка");
        SetCustomToken(2011, "пришла");
        SetCustomToken(2012, "ла");
        SetCustomToken(2013, "могла");
        SetCustomToken(2014, "красавица");
        SetCustomToken(2015, "гостья");
        SetCustomToken(2016, "ая");
        SetCustomToken(2017, "ла");
        SetCustomToken(2018, "ка");
        SetCustomToken(2019, "госпожа");
        SetCustomToken(2020, "женщина");
    }
    return TRUE;
}

Теперь если в диалоге напишем
CODE

Добрый день <CUSTOM2006> <CUSTOM2019>

и поставим этот скрипт в условие показа скрипта, то в зависимости от пола НПС будет говорить «Добрый день мой господин» или «Добрый день моя госпожа»
Для английского языка (и других, на которые переведена игра) проще, там токены можно выбрать прямо в диалоге.

Надеюсь идея понятна – метка позволяет нам из скрипта менять содержимое диалогов, журнала и много чего еще.

При работе над шардом стоит помнить, что инициализировать метки нужно в каждой ветке диалога перед самым использованием. Ведь на шарде играет много людей, и если вы начали диалог, а в это время другой игрок начал другой диалог, использующий те же токены – возможны наложения.

К сожалению метки плохо работают в именах объектов. Если вы зададите имя объекта в виде <CUSTOMZZZZ> и инициализируете токен – на существующие объекты это не окажет влияния. Если вы создадите новый объект – имя изменится на заданное вами значение. Но если вы измените это значение – имя на объекте останется не изменным до смены локации игроком.
Это применимо к именам placable object’ов, с item’ами даже такое не работает.
Поле “description” тоже не изменится.
Получить значение метки из скрипта тоже нельзя.
Добавлено в [mergetime]1128183350[/mergetime]
Попозже допишу про динамические диалоги, как сделать один диалог для множества действий.
20 Sep 2005
В свое время Kristin@ переделала проверку статуса шарда с C# на php.
Как оказалось народ до сих пор ищет подобные скрипты, поэтому кину сюда вариант.

status.php
CODE

<?php
$address='udp://viking.r86.net';   #Адрес шарда
$port=5121;                        #и порт.
$refresh=120;                      # время обновления в секундах

###################################################
###################################################

$status = fsockopen($address,$port);
socket_set_timeout($status, 000010);
fwrite($status,"\x42\x4E\x58\x49\x00\x14");
$_data = fread($status, 5000);
if($_data)
{
  $_data = explode(ord("|"),substr($_data,10,2));
  $_data = unpack("C*", $_data[0]);
  $result['players']     = $_data[1];
  $result['players_max'] = $_data[2];
  $result['color']    = '#078F07';
}
else
{
  $result['players']     = 0;
  $result['players_max'] = 0;
  $result['color']    = '#FF2020';
}
fclose($status);

###################################################
echo "<meta http-equiv='REFRESH' content='$refresh'>";
echo '<body style="background: '.$result['color'].';margin: 0 0 0 0; padding 0 0 0 0;font-family: Arial;font-size: 12px;color:yellow">';
echo $address.":".$port."[";
echo $result['players'].'/'.$result['players_max']."]";

?>



Удобно расположить на рабочем столе, с помощью ActiveDesktop.

Пример работы тут
19 Sep 2005
В разговоре с virusman'ом мы немного обсудили возможность и варианты объеденения форума и шарда, а в перспективе и возможность объеденить форум и шарды.

Я кратко "распишу" прелести такого объединения, технически сейчас возможные, но не реализованные:
  • Единая регистрация. Вы регистрируете свой аккаунт, после входа можете выбирать любого персонажа (из тех, что есть на шарде\шардах).
  • На форуме отображается портрет персонажа (с возможностью залить свой в формате НВН, он же будет использоваться как портрет персонажа и предлагаться для загрузки другим игрокам шарда)
  • На форуме отображается информация о персонаже - класс, раса, уровень, золото, мировозрение (характеристики и абилки - если кто хочет с этими персонажами играть по правилам настолки прямо на форуме)
  • Возможность получать "экспу" и золото прямо на форуме, за полезные для шарда\форума дела. Т.е. это уже не просто попугаи, а вполне себе деньги, на которые можно купить вещь своему персонажу.
    Было в шутку сказано предложение - за каждое сообщение по 1 xp, за флуд - -100 xp, предупреждение карается делевелом. Игрок пять раз подумает, прежде чем писать гадость или флудить :)
    В то же время "летописцы" шарда получают опыт, деньги за творчество на форуме.
  • Возможность сделать приличную торговлю. Игрок на шарде кладет вещь в спец. ящик, игроки уже не в игре, а на форуме, в спец. разделе торговля, могут делать свои ставки (золото сихнронизируется с шардом), кто победил - получает по почте (или любым другим способом) свою покупку.
  • Можно превратить форум в некий вариант текстовой РП-версии шарда, где можно проводить партии, основываясь на персонажах с шарда, которые смогут там получать опыт и деньги. Тут правда не обойтись без присмотра ДМа. Понятно, что выдавать золото или опыт смогут только ДМ-ы игрокам своего шарда и одна партия расчитана на игроков одного шарда (или персонажей, не приписанных к шарду вообще).
  • Далее пункты более далекие от реальности, но как перспектива развития идеи: Сделать единый форум для нескольких шардов. Сам форум будет обмениваться с шардом через mySQL базу данных, так что тут только вопрос нескольких скриптов на шарде.
  • Опять же в идеале - возможность торговли между шардами, с поддержкой разных курсов. Тут все сложнее, вещи из хаков сразу пролетают и ряд других програм для обмена объектами типа leto
Все перечисленное реализуется, причем ничего сложного нет. Есть только объем работ.
Если тема заинтересует достаточное количество людей - можно продумать все и приниматься за работу.\
Предлагается за основу взять "чистую" версию свободнораспространяемого форума и доработать ее, то же самое сделать с шардом - взять новый модуль и реализовать в нем все возможности. Чтобы потом все, кто участвовал в проекте, могли внедрить подобное у себя или же даже объеденить персонажей с шардов, чтобы не требовать регистрации на каждом форуме, а брать данные из общей базы, которая синхронизирует информацию с реальными шардами.

Просьба не просто обсуждать, но добавлять "стал бы участвовать", "не стал бы" "не знаю", в первом случае просьба добавлять "знаю [php][mySQL][nss][nwnx][nwnx-plugs]"
Последние посетители


18 Apr 2022 - 16:28


7 Feb 2020 - 1:45


9 Oct 2016 - 22:38


26 Jun 2015 - 14:17


7 Feb 2015 - 22:40

Комментарии
Вам не оставили ни одного комментария _kaa_.

Друзья
Нет друзей для показа.
Текстовая версия Сейчас: 19th March 2024 - 08:44