Игроделу | Скрипты | Neverwinter Nights
По сайту
Главная
Рыцарская сага
Файлы
Neverwinter Nights
Гостевая книга
Форум сайта

По теме
Игроделу
Редактор фракций
Редактор журнала
Редактор диалога
Редактор скрипта
Модуль и области
Двери
Монстры
Встречи
Предметы
Торговцы
Объекты
Триггеры
Звуки
Тайлы
Хак файлы
Скриптинг
Neverwinter NightsскриптыИгроделу

Скрипты - Neverwinter Nights

Страница: 1 2 3
if, else switch while for операторы сравнения арифметические операторы

Эта тема, надеюсь, поможет вам научиться писать простые скрипты для Neverwinter Nights (NWN). Все скрипты мы пишем в “Редакторе скрипта”. Это программа поддерживает компиляцию и служит только для записи и программирования скриптов. При компиляции, создается два файла с расширением NSС и NSS. Для игры используются файлы с расширение NSC, а файлы NSS это ни что иное, как средство программирования, без которого игра вполне может обойтись… Все эти файлы можно просмотреть во временной папке temp0, которая создается при открытие модуля в папке modules, находящейся в корневой папке игры.

Скрипты бывают трех видов:

  1. Скрипты исполнения записываться с оператором void main().
  2. Скрипты проверки записываться с оператором int StartingConditional(). Служат в основном для использование в “Редакторе диалога” для проверки события на ветке диалога
  3. Скрипты библиотеки #include где могут быть записаны константы и функции. Эти скрипты записываются только с расширением NSS, и служат для обращения к ним других скриптов при компиляции. Этот скрипт достаточно просто сохранить в редакторе без компиляции.

Чтобы не забыть, что у нас записано, или просто пояснить скрипт, мы используем для комментариев два оператора: // или /* */
Пример:

  1. // Комментарий строки
  2. /* Комментарий любого
       исходного абзаца */

Давайте попробуем записать первый скрипт. Для примера возьмем присвоение локальной переменной объекту. Для этого найдем в редакторе функцию void SetLocalInt, и посмотрим записанные для нее комментарии.

// Set oObject's local integer variable sVarName to nValue
void SetLocalInt(object oObject, string sVarName, int nValue)

Итак, что это такое? Образно, локальная переменная это строка которая вписывается в служебное место любого объекта и состоит из буквенной строки и числового значения. Видите эта функция состоит из трех частей: Объект, Стринг, Число. Попросту говоря мы вешаем на объект ярлык с названием и ценой.

NWN использует основные операторы: action, const, effect, event, float, int, itemproperty, location, object, string, struct, talent, vector, void… Более подробно о каждом типе читаем здесь. Ниже, для примера мы рассмотрим подробно только три типа наших операторов:
object, string, int.

object (объект)
Этим типом обозначаются все объекты: создания, триггеры, размещаемые предметы, инвентарь, вейпоинты… Также имеются еще две постоянных для обозначения объекта. Это OBJECT_SELF для объекта владельца скрипта. И OBJECT_INVALID несуществующий или неправильно заданный объект.
Пример:
object oSelf = OBJECT_SELF;
object oNPC = OBJECT_INVALID;
object oPC = GetFirstPC();

string (строка)

Это строчный тип переменной, в которой содержится любой текст. В тексте могут быть не только буквы, но и цифры, знаки, символы. Чтобы отнести написанное к тексту, его заключают в кавычки.
Пример:
string sName = “Gennady”;
string sFemale = “Иванов”;

int (целое число)
Этот тип представляет 32-битное целое число. Максимальное значение 2,147,483,647, минимальное значение -2,147,483,648, по умолчанию 0. Также у нас имеется две постоянных величины: TRUE, FALSE. Это константы интов, и определены они так:
int FALSE = 0;
int TRUE = 1;

Все значения зарезервированных переменных можно просмотреть в скрипте ресурса игры под именем “nwscript”.

Пример:
int iTime = 0; Это же можно записать и так: int iTime; или int iTime = FALSE;
int iNum = 1; Это же можно записать и так: int iNum = TRUE;
int iNew = 3; // Значение равно трем

Скрипт мы будем записывать с использованием оператора void main(). Сама запись скрипта должна находиться между фигурными скобками {…}. Этот скрипт можно будет использовать, допустим, для отметки события о первом диалоге героя с неким персонажем. Поэтому определим, что нам нужно повесить локалку на Главного Героя, значит, наш объект можно пробить как GetFirstPC() или GetPCSpeaker(). Посмотрите какое определение дает редактор скрипта для этих функций:

// Get the first PC in the player list.
// This resets the position in the player list for GetNextPC().
object GetFirstPC()

// Get the PC that is involved in the conversation.
// * Returns OBJECT_INVALID on error.

object GetPCSpeaker()

Главное усвоить, что именно эти функции являются операторами object. Имя мы им можем задать любое. Оно у нас будет определенно только для этого скрипта, т.ч. можно не боятся в другом скрипте брать аналогичное. Обычно в скриптах Главного Героя обозначают именем oPC. Обозначаем имена переменных операторов по такой схеме:
[оператор] [имя оператора] [;]

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

  1. object oPC; // главный герой
  2. object oPC, oNPC, oSelf; // герой, некий персонаж, носитель скрипта

В нашем скрипте будет всего один объект, т.е. наш герой, поэтому удобно сразу присвоить оператору object логическое значение:
Например:
object oPC = GetPCSpeaker(); // ГОВОРЯЩИЙ персонаж

Получается, мы нашему оператору object с именем oPC, присвоили значение говорящего в диалоге персонажа. Хочу сразу отметить, что в диалоге, на какой бы ветке не стоял скрипт, герой всегда является говорящим персонажем, а вот объект у которого стоит этот диалог, всегда является OBJECT_SELF.

Запишем, наконец, сам скрипт:

//::////////////////////////////////////////////
//:: Присвоить РС переменную FIRST_DIALOG = 1
//:: File name: script_n_1
//::////////////////////////////////////////////
void main()
{
object oObject  = GetPCSpeaker(); // ГОВОРЯЩИЙ персонаж
string sVarName = "FIRST_DIALOG"; // ИМЯ переменной
int nValue = 1; // Цена переменной 
// void SetLocalInt(object oObject, string sVarName, int nValue)
        SetLocalInt(oObject, sVarName, nValue);
}
/* Присвоить герою переменную FIRST_DIALOG равную единице. */

Как видите, я воспользовался именами операторов прописанных в функции, задал им значения, а потом записал функцию удалив из нее эти переменные операторы (void, object, string, int). Для наглядности я вписал эту функцию в виде комментария. Подобным образом можно записать любую функцию, но для лучшего чтения скрипта лучше немного изменить скрипт, особенно если в скрипте будет не одна локалка.

И еще один маленький совет. Пишите шапку скрипта, где укажите как минимум что делает скрипт и его имя. Это очень поможет в работе в текстовом редакторе (шапка будет видна в окне просмотра), ну и для копирования имени скрипта и последующего запуска его из других скриптов. Особенно это актуально для библиотек.

Итак, перепишем наш скрипт:

//::////////////////////////////////////////////
//:: Присвоить РС переменную FIRST_DIALOG = 1
//:: File name: script_n_2
//::////////////////////////////////////////////
void main()
{
  object oPC  = GetPCSpeaker(); // ГОВОРЯЩИЙ персонаж  
  SetLocalInt(oPC, "FIRST_DIALOG", 1);
}
/* Присвоить герою переменную FIRST_DIALOG равную единице. */

Теперь у нас в одной строке сразу видно имя переменной (локалки), и ее числовое значение. Поставив этот скрипт в ветку первого диалога NPC (нового персонажа), в слот “Совершены действия”, мы при диалоге зададим герою локалку об этом событии. Логично, что теперь ее нужно как-то проверить. Для этого и служит скрипт проверки, записываемый с оператором int StartingConditional(). Этот скрипт ставиться в слот “Текст появляется при…”

Прежде чем записать скрипт, давайте еще разберем оператор if.

Операторы if, else пишутся в начале строки, далее в скобочках мы задаем условие, а еще дальше в той же строке, что должно исполниться (строка всегда заканчивается точкой с запятой [;]). После строки if, можно записать строку else, т.е. в переводе еще или иначе. Если выполнится логическое выражение в скобочках, то скрипт прочитает и выполнит строку if, а строку else даже не будет читать. А вот если условие не выполнено, то скрипт перейдет сразу на чтение строки else, и у нас получится, что выполнилнится только строка else. Если мы в скрипте не используем else, то программа просто продолжит чтение скрипта, не дочитав и не исполнив строку if.

В обобщенной форме эти операторы записывается следующим образом:
if (логическое выражение) оператор1; else оператор2;

Раздел else необязателен. На месте любого из операторов может стоять составной оператор, заключенный в фигурные скобки.
if (логическое выражение) {оператор1; оператор2;} else {оператор3; оператор4;}

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

Операторы сравнения

СимволОписание
= =Тест равенства ("равняется")
!=Тест не равенства ("не равняется")

<

Меньше чем, первый оператор
>Больше чем, первый оператор
<=Меньше или равно первому оператору
>=Больше или равно первому оператору
&&Логическое И
&Битовое И
||Логическое ИЛИ
|Битовое ИЛИ
!Логическое НЕ

Оператор int StartingConditional() возвращает значение int и должен иметь хотя бы один оператор возврата return в пределах своего кодового блока. Причем, если возвращаемое значение равно нулю, то это будет означать, что проверка провалена, и ветка диалога не откроется. Теперь запишем скрипт:

//::////////////////////////////////////////
//:: Проверить переменную FIRST_DIALOG
//:: File name: script_n_3
//::////////////////////////////////////////
int StartingConditional()
{
  object oPC = GetPCSpeaker(); // ГОВОРЯЩИЙ персонаж
  int iProv = GetLocalInt(oPC, "FIRST_DIALOG"); // Числовое значение локалки FIRST_DIALOG
  if(iProv == 1) return TRUE;  // Прошли проверку
  else return FALSE;  // Не прошли проверку
}

Теперь разберем как работает скрипт. В первой строке мы задали объект, т.е. взяли нашего героя. Вторая строка, это переменный инт с именем требуемой локалки. Эту функцию легко найти в фильтре функций, если набрать LocalInt. По определению у нее записано:
// Get oObject's local integer variable sVarName
// * Return value on error: 0

int GetLocalInt(object oObject, string sVarName)

Поэтому я задал оператору iProv требуемые значения, т.е. выбрал персонаж object oObject(oPC), на котором нужно искать эту переменную, и ее название string sVarName (FIRST_DIALOG).

Затем при помощи if (если) проверяем, действительно ли наш инт iProv равен единице (iProv == 1). Если равен, по дальше мы записали код возврата (return TRUE), т.е. присвоение нашему int StartingConditional() значения TRUE, т.е. единице. Если условие не выполнилось, программа читает скрипт дальше, else (иначе) возвращает значение инта StartingConditional FALSE, т.е. нулевое значение (return FALSE). Попросту сказать, скрипт работает как переключатель ДА, НЕТ.

Мы рассмотрели два вида скриптов, исполнения и проверки, осталось разобрать как записать библиотеку – инклюду (#include). Но сперва давайте попробуем составить простенькую функцию, т.к. некоторые игровые моменты можно сделать только с использованием новых функций. Например, вызвать скриптом новый объект из палитры в заданное время. Стандартная функция вызова CreateObject не поддерживает оператор DelayCommand, поэтому используем ее для нашего примера.

Давайте сначала разберем, как использует программа оператор DelayCommand. Это, если можно сказать, таймер времени, стоящий на объекте владеющим скриптом. Сама функция таймера DelayCommand так записана в редакторе:
void DelayCommand(float fSeconds, action aActionToDelay)

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

float (число с плавающей точкой)
Этот тип представляет 32-битное число с плавающей точкой. Максимальное значение 3.402823e38, минимальное значение -3.402823e38, по умолчанию 0.0.Во всех переменных типа float нужно обязательно ставить десятичную точку и хотя бы одну цифру после нее.

Функция (action aActionToDelay) может быть любая, главное чтобы она начиналась с оператора void. Операторы функции вызова мы не будем менять, изменим только ее имя. Так она выглядит по определению:
object CreateObject(int nObjectType, string sTemplate, location lLocation, int bUseAppearAnimation=FALSE, string sNewTag="")

Видите, что это функция объект, а значит, по условию ее не может использовать оператор DelayCommand. Запишем сразу скрипт с использованием новой функции:

//::////////////////////////////////////////////
//:: ВЫЗВАТЬ ИЗ ПАЛИТРЫ ПЛАКАТ
//:: File name: script_n_4
//:://////////////////////////////////////////// 
void CreateObjectNew(int nObjectType, string sTemplate, location lLocation, 
     int bUseAppearAnimation=FALSE, string sNewTag="")
{
 CreateObject(nObjectType, sTemplate, lLocation, bUseAppearAnimation, sNewTag);
}
void main()
{
 object oWP1 = GetWaypointByTag("POINT_1"); // Точка 1
 object oWP2 = GetWaypointByTag("POINT_2"); // Точка 2
 location Loc1 = GetLocation(oWP1); // ЛокациЯ первой точки
 location Loc2 = GetLocation(oWP2); // ЛокациЯ второй точки
 string sResRef = "plc_placard1"; // ResRef вызываемого объекта

 object oPlace = CreateObject(OBJECT_TYPE_PLACEABLE, sResRef, Loc1);
 DelayCommand(10.0, CreateObjectNew(OBJECT_TYPE_PLACEABLE, sResRef, Loc2));
}

В скрипте я записал новую функцию с именем CreateObjectNew, и с использованием оператора void. Она записана перед главным оператором скрипта исполнения (void main()). Новая функция имеет те же операторы, что и базовая. Дальше, в фигурных скобочках, мы задали код исполнения новой функцией. По задумке, нам достаточно просто вызвать объект из палитры. Поэтому мы и записываем функцию вызова, с использованием обозначенных в новой функции операторов.

Общая схема построения функции, выглядит так:

[оператор][имя оператора][используемые операторы]{исполняемый код}

Мы задали программе новую функцию, которую и используем дальше в скрипте. В скрипте записан вызов двух объектов (плакатов) из стандартной палитры. Первый, я его сразу определил как объект, вызывается стандартной функцией редактора CreateObject. Второй плакат, будет вызван через 10 секунд, нашей новой функцией CreateObjectNew.

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

Стоит отметить еще одну особенность записи функций. Если вы обратили внимание, то два последних оператора нашей функции (int bUseAppearAnimation=FALSE, string sNewTag="") уже имеют определенное значение. Это значит, что мы можем в скрипте не обозначать эти переменные, тогда скрипт прочитает их как ранее заданные, т.е. FALSE и [""]. По этой причине я и не прописал их в скрипте. Помните, что операторы, с заданными величинами, пишутся в конце строки перечисления используемых операторов.

Записать новую функцию можно тремя способами. Первый мы разобрали, т.е. функция записывается до основного скрипта. Второй способ это просто перед скриптом обозначить нашу функцию (т.е. записать название и основные операторы, а в конце строки поставить точку с запятой [;]), а после основного блока скрипта записать полностью функцию. Смотрим вторую схему:

//::////////////////////////////////////////////
//:: ВЫЗВАТЬ ИЗ ПАЛИТРЫ ПЛАКАТ
//:: File name: script_n_5
//::////////////////////////////////////////////

// ВЫЗВАТЬ ОБЪЕКТ ИЗ ПАЛИТРЫ 
void CreateObjectNew(int nObjectType, string sTemplate, location lLocation,
                     int bUseAppearAnimation=FALSE, string sNewTag="");
void main()
{
 object oWP1 = GetWaypointByTag("POINT_1"); // Точка 1
 object oWP2 = GetWaypointByTag("POINT_2"); // Точка 2
 location Loc1 = GetLocation(oWP1); // ЛокациЯ первой точки
 location Loc2 = GetLocation(oWP2); // ЛокациЯ второй точки
 string sResRef = "plc_placard1"; // ResRef вызываемого объекта

 object oPlace = CreateObject(OBJECT_TYPE_PLACEABLE, sResRef, Loc1);
 DelayCommand(10.0, CreateObjectNew(OBJECT_TYPE_PLACEABLE, sResRef, Loc2));
}
/* Конец основного блока кода скрипта */
void CreateObjectNew(int nObjectType, string sTemplate, location lLocation, 
     int bUseAppearAnimation=FALSE, string sNewTag="")
{
 CreateObject(nObjectType, sTemplate, lLocation, bUseAppearAnimation, sNewTag);
}

Смотрите, что изменилось. Мы перед основным кодом скрипта, задали нашу функцию, а прописали ее после скрипта (обратите внимание, я выше функции написал еще комментарий). Спросите, зачем писать столько лишнего? Правильно, поэтому и делаются библиотеки, а это и есть третий способ подключить к скрипту новую функцию. Теперь давайте запишем нашу функцию в библиотеку:

//::////////////////////////////////////////////
//:: БИБЛИОТЕКА  #include"include_first"
//::////////////////////////////////////////////

// ВЫЗВАТЬ ОБЪЕКТ ИЗ ПАЛИТРЫ
// nObjectType : OBJECT_TYPE_ITEM, OBJECT_TYPE_CREATURE,
// OBJECT_TYPE_PLACEABLE, OBJECT_TYPE_STORE, OBJECT_TYPE_WAYPOINT
// sTemplate: ResRef вызываемого объекта
// location lLocation: локациЯ где поЯвитсЯ объект
// bUseAppearAnimation: если TRUE, то эффект падениЯ с неба
// sNewTag: новый тэг вызываемого объекта
void CreateObjectNew(int nObjectType, string sTemplate, location lLocation,
                     int bUseAppearAnimation=FALSE, string sNewTag="");
////////////////////////////////////////////////////////////////////////////////
void CreateObjectNew(int nObjectType, string sTemplate, location lLocation,
                     int bUseAppearAnimation=FALSE, string sNewTag="")
{
 CreateObject(nObjectType, sTemplate, lLocation, bUseAppearAnimation, sNewTag);
} //void main(){}

Как видите, я записал комментарии, ниже определил функцию и ее операторы, потом уже саму функцию и ее код. Теперь если вы введете в фильтр поиска название функции CreateObjectNew, появится наша функция с жирным шрифтом. В нижней панели “Помоги” редактора, появятся наши комментарии и само определение функции.

Чтобы появились в панели “Помоги” комментарии, строка определения функции должна быть записана одной строкой, тогда все строки выше функции CreateObjectNew, начинающиеся с операторов [//], будут выведены в панель помощи, как и само определение функции. (см. пример инклюды в “Редакторе скрипта”)

В самом низу скрипта библиотеки, я поставил [//void main(){}] как комментарий. Это сделано для удобства проверки кода библиотеки. Если снять оператор комментария [//], и снова компилировать скрипт, то редактор покажет, есть ли ошибки в коде. Если ошибок нет, то ставим опять эту строку, как комментарий, а сам скрипт просто сохраняем.

Давайте запишем это же скрипт вызова двух плакатов, но уже с подключением нашей библиотеки:

//::////////////////////////////////////////////
//:: ВЫЗВАТЬ ИЗ ПАЛИТРЫ ПЛАКАТ
//:: File name: script_n_6
//::////////////////////////////////////////////
#include"include_first"
void main()
{
 object oWP1 = GetWaypointByTag("POINT_1"); // Точка 1
 object oWP2 = GetWaypointByTag("POINT_2"); // Точка 2
 location Loc1 = GetLocation(oWP1); // ЛокациЯ первой точки
 location Loc2 = GetLocation(oWP2); // ЛокациЯ второй точки
 string sResRef = "plc_placard1"; // ResRef вызываемого объекта

 object oPlace = CreateObject(OBJECT_TYPE_PLACEABLE, sResRef, Loc1);
 DelayCommand(10.0, CreateObjectNew(OBJECT_TYPE_PLACEABLE, sResRef, Loc2));
}

Сноска на инклюду всегда пишется до основного оператора скрипта. Тогда при компиляции, программа берет нужную функцию из библиотеки и записывает код с расширением NSC. Нам же остается сохраненный код с расширением NSS, для визуального просмотра. Помните, если вы сменили код в библиотеке, то нужно обязательно компилировать все скрипты, в функциях которых произошли изменения. Иначе скрипт будет работать по старой компиляции. Если же вы вносите в библиотеку новую функцию, то старые скрипты с использованием библиотеки, компилировать не нужно, ведь в них нет этой новой функции.

Теперь давайте разберемся более подробно с некоторыми операторами и их использованием в скриптах. Начнем с двух операторов генерации случайных целых чисел. Первый, так называемые дайсы имеет несколько вариантов записи (d2(), d3()… d100()), вот комментарий редактора этого оператора:
// Get the total from rolling (nNumDice x d10 dice).
// - nNumDice: If this is less than 1, the value 1 will be used.
int d10(int nNumDice=1)

Разберем, как работает оператор, допустим d3(). Если у нас в скобочках нет ни какого числа, то будет случайное число от 1 до 3, т.е. 1, 2, 3. Другими словами оператор берет случайную цифру из ряда целых чисел от единицы, до числа записанного после оператора d. По условию инт nNumDice у нас равен единице. Это не что иное как множитель, если мы зададим другое число, например 10 - d3(10), то у нас получиться такие величины случайных чисел:

  1. первая 1х10=10
  2. последняя 3х10=30

Получаем такой ряд случайных чисел: 10, 11, … 29, 30. Из этого ряда оператор и возьмет случайное число.

Второй оператор случайных чисел – это Random(). Смотрим комментарий редактора:
// Get an integer between 0 and nMaxInteger-1.
// Return value on error: 0
int Random(int nMaxInteger)

Разбираемся, ряд случайных чисел теперь у нас начинается с нуля, а заканчивается величиной инта nMaxInteger.
Например:

  1. Random(3) имеем ряд: 0,1, 2.
  2. Random(10) имеем ряд: 0,1, ... 9.

Давайте запишем скрипт для диалога, чтобы проверить работу наших генераторов цифр. Можно этот скрипт поставить и на вход триггера:

//::////////////////////////////////////////////
//:: ГЕНЕРАЦИЯ СЛУЧАЙНЫХ ЧИСЕЛ
//:: File name: script_n_7
//::////////////////////////////////////////////
void main()
{
 int iDise1 = d3();
 int iDise2 = d3(10);
 int iRandom1 = Random(3);
 int iRandom2 = Random(10);
 string sText = IntToString(iDise1); // Перевод числовой величины в строчный код
 string sInfa = "Случайное число: ";

 SpeakString(sInfa+"d3() = "+sText);
 sText = IntToString(iDise2);
 DelayCommand(3.0, SpeakString(sInfa+"d3(10) = "+sText));
 sText = IntToString(iRandom1);
 DelayCommand(6.0, SpeakString(sInfa+"Random(3) = "+sText));
 sText = IntToString(iRandom2);
 DelayCommand(9.0, SpeakString(sInfa+"Random(10) = "+sText));
}

Оператор switch

Операторы генерации случайных целых чисел нужны для записи скриптов с использованием оператора switch. Оператор switch действует подобно серии операторов if, с пошаговой проверкой совпадения с величиной оператора case. Обычно после прочтения кода блока оператора case, ставят оператор остановки цикла break. Это не обязательное условие, и если нужно, то можно продолжить чтение оператора switch.

Если же значению выражения не соответствует ни один из операторов case, управление передается коду, расположенному после ключевого слова default. Отметим, что оператор default необязателен. В случае, когда ни один из операторов case не соответствует значению выражения и в switch отсутствует оператор default, выполнение программы продолжается с оператора, следующего за оператором switch.

Общая форма этого оператора такова:

switch ( выражение )
{
case значение1: [операторы]
break;

case значение2: [операторы]
break;

default: [операторы]
}

Подобная схема построения скрипта подходит для генерации случайной вещи в контейнере, или случайной фразы персонажа. По этой схеме удобно записывать в один скрипт проверку и обработку разных сигналов, сообщенных объекту. Например:

//::////////////////////////////////////////////
//:: ГЕНЕРАЦИЯ СЛУЧАЙНЫХ предметов и фраз
//:: File name: script_n_8
//:: СЛОТ: OnOpen контейнера
//::////////////////////////////////////////////
void main()
{
  string sItem, sText;

// Дать случайный предмет
switch (d3())
{
 case 1: sItem = "nw_wplhb001"; break;
 case 2: sItem = "nw_ashto001"; break;
 case 3: sItem = "nw_wblhw001"; break;
}
CreateItemOnObject(sItem);

// Произнести случайную фразу
switch (Random(6))
{
 case 0: sText = "Не тобой положено!"; break;
 case 2: sText = "Караул! На помощь!"; break;
 case 4: sText = "Воры! Воры!"; break;
 default: sText = "Бери еще монеты, чего уж там..."; // Шанс 50%
 CreateItemOnObject("nw_it_gold001", OBJECT_SELF, 100); // Дать 100 монет золота
}
  SpeakString(sText);
}

Циклы (while, for)

Оператор while служит для циклического (кругового) чтения одного и того же куска кода. Это бывает просто необходимо, когда нужно найти какой-то объект, или проверить логическое условие, среди множества подобных.

Любой цикл можно разделить на 4 части — инициализацию, тело, итерацию и условие завершения. while - этот цикл многократно выполняется до тех пор, пока выполняется значение логического выражения. Ниже приведена общая форма оператора while:
[инициализация;]
while (завершение)
{ тело; [итерация;] }

Инициализация и итерация необязательны.

Инициализация – это определение всех исходный операторов для запуска первого цикла, если конечно такие нужны. Тело – это основной блок кода цикла. Итерация – это запуск следующего цикла, но если нужно прочитать цикл один раз, или прервать цикл, в определенном месте кода цикла, то для этого ставиться оператор break. Для запуска обычно используют увеличение или уменьшение на единицу, величины проверяемого циклом целого числа (int), или изменение логического определения. Для этого используют арифметические операторы.

Арифметические операторы (используются для выполнения операций над типами int и float)

СимволОперацияОписание
+СложениеПростое сложение. Для string, как сложение строк.
-ВычитаниеПростое вычитание
*УмножениеПросто умножение. При умножении на 0 всегда получишь 0, при умножении на 1 всегда получишь умножаемое число
/ДелениеПростое деление. При делении на 0 происходит ошибка "Деление на ноль".
%МодульВычисляет остаток оператора деления (нет результата; работает только для целых)
++ИнкрементУвеличивает значение переменной на один
--ДекрементУменьшает значение переменной на один
=ПрисваиваниеПрисвоить значение выражения в переменную.
Для string присваивает значение выражения в переменную строки.
+=Сложение с присваиваниемПрисваивает значение от сложения переменой с заданным числом.
Для string присваивает значение от сложения текста переменной и указанным текстом
-=Вычитание с присваиваниемПрисваивает значение от вычитания из переменной указанного числа
*=Умножение с присваиваниемПрисваивает значение от умножения переменной на заданное число
/=Деление с присваиваниемПрисваивает значение от деления переменной на заданное число
%=Модулю с присваиваниемПрисваивает значение от извлечения целой части из переменной

Давайте запишем через цикл, всплывающий текст из скрипта script_n_7. В том скрипте мы выводили значение случайного числа через каждые три секунды. Я немного упрощу скрипт для лучшего понимания процесса. У нас будет всего один ряд случайных чисел и немного поменяется строка текста. Наш цикл:

//::////////////////////////////////////////////
//:: ГЕНЕРАЦИЯ СЛУЧАЙНЫХ ЧИСЕЛ И ВЫВОД ТЕКСТА
//:: File name: script_n_9
//::////////////////////////////////////////////
void main()
{
 int iRandom = Random(10);
 string sInfa = "Случайное число: ";
 float fTime;

  while(fTime < 9.0)
  {
   string sText = IntToString(iRandom); // Перевод числовой величины в строчный код
   DelayCommand(fTime, SpeakString(sInfa+sText));
   iRandom = Random(10);  // Новое случайное число
   fTime+=3.0; // Прибавим три секунды
  }
  DelayCommand(fTime, SpeakString("Конец цикла!"));
}

Разбираем код. Первая строка, задали случайное число iRandom из ряда от 0, 1, … до 9. Вторая, определили строку sInfa. Третья строка, это наша инициализация цикла, флот fTime (десятичное число) задали по умолчанию, т.е. его величина равна 0.0 – а это значит, что первый цикл запустится, т.к. логическое выражение (0.0 < 9.0) верно.

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

Последняя строка кода цикла – это итерация цикла, т.е. мы меняем значение исполнения цикла. Я использовал оператор сложение с присваиванием [+=], но это выражение можно записать и по-другому, например:
fTime+=3.0; или fTime= fTime +3.0;

Другими словами, мы прибавляем к первоначальному времени три секунды: 0.0+3.0=3.0. Первый цикл закончен, оператор while, продолжает работать, и сравнивает новое логическое выражение (3.0 < 9.0), что опять верно, значит, начинается второй цикл. Вот все значение fTime, что у нас получатся в циклах:

  1. fTime=3.0 таймер фразы DelayCommand(0.0, SpeakString(sInfa+sText));
  2. fTime=6.0 таймер фразы DelayCommand(3.0, SpeakString(sInfa+sText));
  3. fTime=9.0 таймер фразы DelayCommand(6.0, SpeakString(sInfa+sText));

Пройдя три цикла, при значениеfTime равном 9.0, логическое выражение примет вид (9.0 < 9.0), что не верно. Это и будет завершение цикла, дальше программа прочитает последнею строку скрипта, причем значение времени fTime у нас уже будет 9.0, т.е. эта фраза появиться после 3 секунд, после последней фразы из цикла.

В скриптах циклов Neverwinter Nights широко используется оператор GetIsObjectValid, для которого также имеется целый ряд вспомогательных операторов инициализации:
GetFirstItemInInventory, GetFirstObjectInArea, GetFirstObjectInShape, …
а также ряд итераций:
GetNextItemInInventory, GetNextObjectInArea, GetNextObjectInShape, …
Давайте разберем как работает цикл с использованием оператора GetIsObjectValid. Задача скрипта, найти в инвентаре героя два кинжала и удалить их, заплатив герою 100 монет золота за каждый. Наш скрипт:

//::////////////////////////////////////////////
//:: ЗАБРАТЬ ДВА КИНЖАЛА И ДАТЬ ЗОЛОТО 
//:: File name: script_n_10
//::////////////////////////////////////////////
void main()
{
 object oPC = GetFirstPC(); //Наш герой
 object oItem = GetFirstItemInInventory(oPC); // Первый предмет в инвентаре
 int iNum;

  while(GetIsObjectValid(oItem))
  {
   if(GetBaseItemType(oItem) == BASE_ITEM_DAGGER)
    {
     DestroyObject(oItem); // Удалить кинжал
     GiveGoldToCreature(oPC, 100); // Дать золото
     iNum++;
    }
   if(iNum == 2) break; // Остановить цикл
   // SendMessageToPC(oPC, "Предмет: <c у >"+GetName(oItem)+"</c>");
   oItem = GetNextItemInInventory(oPC); // Следующий предмет в инвентаре
  }
 if(iNum == 0) SpeakString("Ба-а! Да у вас нет ни одного кинжала!");
 else SpeakString("Спасибо!");  
}

Разберем скрипт. Мы задали oItem, как первый объект в инвентаре героя oPC, если он есть, то цикл начнется. Еще мы задали переменную целого числа iNum, это нужно, чтобы отследить количество удаленных кинжалов.

Небольшое отступление. Оператор GetIsObjectValid работает не только на проверку действительности объектов, но и как своего рода считалка, т.е. первый объект, считается всего один раз, поэтому не происходит зацикливания. Но есть одна тонкость, когда мы удаляем объект, то это сбивает код, т.к. удаление занимает некоторое время (1-1,5 сек.), и оператор зацикливается, т.е. происходит несколько подсчетов удаляемого объекта. Это не критично когда мы просто удаляем объекты, но вот если при этом идет какое-то присвоение, как-то выдача золота, то стоит учитывать этот нюанс…

Хорошо, продолжим, первый цикл запущен, дальше идет проверка первого предмета, кинжал ли это. Если кинжал, то мы удаляем объект, даем золото и увеличиваем наш инт iNum на единицу. У нас записано это выражение с использованием оператора “Инкремент”, но можно это же записать иначе. Например:
iNum++; или iNum= iNum+1; или iNum+=1;

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

Следующая строка цикла у нас является итерацией, т.е. имени переменной oItem, мы присваиваем следующий объект инвентаря героя. Выше этой строки я закомментировал код, где может быть выведено в отчет название предмета зеленой строкой в панели отчета. Можете убрать оператор комментария [//] и посмотреть полный отчет о цикле. Если в инвентаре героя есть еще предмет, то цикл продолжится...

Оператор for

В этом операторе предусмотрены места для всех четырех частей цикла. Ниже приведена общая форма оператора записи for.
for (инициализация; завершение; итерация) тело;

Любой цикл, записанный с помощью оператора for, можно записать в виде цикла while, и наоборот (если мы оперируем числовыми значениями). Если начальные условия таковы, что при входе в цикл условие завершения не выполнено, то операторы тела и итерации не выполняются ни одного раза. В каноническая форме цикла for, когда известно число повторений, происходит увеличение целого значения счетчика с минимального значения до определенного предела.

Давайте рассмотрим пример цикла с использованием for, и попутно ознакомимся с такими важными операторами, как: GetObjectByTag, GetNearestObjectByTag, GetNearestObject, GetNearestCreature. Эти операторы включают в себя оператор int nNth=1 – это не что иное, как порядковый номер объекта, в случае GetNearest (объект берется только в области владельца скрипта, а не модуля в случае с оператором GetObjectByTag), нумерация возрастает, с увеличением расстояния от заданного объекта.

Задача скрипта. Выбрать в области три существа, и вывести реплику для каждого с его именем и порядковым номером. Наш скрипт:

//::////////////////////////////////////////////
//:: ОПРОСИТЬ ТРИ СУЩЕСТВА
//:: File name: script_n_11
//::////////////////////////////////////////////
void main()
{
 int nNth; // Номер существа
 for(nNth=1; nNth <= 3; nNth++)
 {
  object oNPC = GetNearestObject(OBJECT_TYPE_CREATURE, OBJECT_SELF, nNth); // Существо
  AssignCommand(oNPC, SpeakString(GetName(oNPC)+" "+IntToString(nNth))); // Фраза
 }
}

Что у нас получилось? Мы задали величину nNth, т.е. это будет наш номер существа. В операторе for, мы придали ей значение равное единице, что позволяет запустить первый цикл, т.к. логическое выражение:
(1 <= 3) верно. В теле цикла мы присваиваем объекту oNPC ближайший объект - существо (OBJECT_TYPE_CREATURE), и оно будет для скрипта под номером один, т.к. nNth=1. Дальше мы выводим фразу, причем мы использовали оператор AssignCommand, который назначает выполнить эту строку кода, именно объекту oNPC, а не владельцу скрипта. Затем работает оператор nNth++ который прибавляет единичку к текущему значению nNth, и при выполнение логического выражения оператора for, у нас запустится очередной цикл. Нетрудно догадаться, что таких циклов будет всего три.

Страница: 1 2 3
Обновление: Rambler's Top100