Ноктюрн влюбленного зомби. Все о редакторе Neverwinter Nights, часть 3
Сегодня мы поговорим о скриптах… Кто там нервно вскрикнул и упал на пол?
Помощь. Обязательно ее используйте.
Скрипты — это прогресс. Вашим пытливым, уважаемые геймеры, умам предоставлена возможность собрать игру своей мечты. Поэтому мужайтесь и готовьтесь к неизбежному — в битве со скриптами должны победить отнюдь не скрипты. С их помощью вы сможете сделать все. Дело за малым — понять, как они действуют, и заставить их работать на себя. А в этом мы вам поможем. Не стоит думать, что сейчас вам незамедлительно откроются все тайны. Для всех тайн есть замечательный help под названием NWN Lexicon , который можно взять с нашего компакта. Однако, если английский язык этого замечательного документа оказался слишком сложен для вас, то начать писать самые замечательные скрипты вам поможет данная статья. Состав скриптов Если подходить к вопросу просто, то основных частей у скриптов немного: события ( events ), функции ( functions ), константы ( constants ) и типы данных ( data types ). Плюс переменные и всевозможные числовые и текстовые данные.События — это слоты, в которые помещаются скрипты. Стоит заметить, что для разных объектов набор скриптовых слотов будет разным. Существу не нужен слот OnClose , это принадлежность двери. Зато у двери нет слота OnRested , она не отдыхает.Функции — непосредственно операторы скриптового языка. Именно с их помощью и проводятся все действия.Константы — способ обозначения класса переменных (а их в редакторе очень много) или заменитель переменных, если речь идет об одноразовом действии.Типы данных — тип переменной или константы (будет она вектором, числовой переменной или текстовой). А теперь — поподробнее о каждой из этих составляющих. События Каждый скрипт нужно приладить к положенному месту. А какое у него может быть место? Да много разных. При открытии по щелчку правой кнопки мыши Свойств объекта вы в самом низу увидите строчку Properties.
Пока здесь все скрипты стандартные. Но вы можете заменить их своими.
Щелкнув на ней, вы увидите собственно свойства объекта. Но конкретно вам нужна закладка Scripts. На ней и расположены слоты. У Area их мало, всего 4. У Creature — много, 13. Дело не в количестве, дело в том, что они предусмотрены на все случаи жизни. Жизни объектов. И даже на случай смерти. Обычно в слотах стоят скрипты “по умолчанию”. Но ведь для того мы сейчас вам и объясняем скрипты, чтобы эти “молчащие” скрипты были заменены на “живые”, авторские. Читайте и запоминайте, что за слоты встречаются и для чего они нужны. Повторяющиеся слоты каждый раз описываться не будут, смотрите их значение у объектов, написанных раньше.Заменять все скрипты, стоящие по умолчанию, на свои собственные не стоит. Ведь писали их не на коленке, а с умом. Лучше будет добавлять свои скрипты в стоящие по умолчанию. В том же слоте OnSpawn есть множество заблокированных скриптов, которые можно использовать в своем модуле. Существо (Creature) OnBloked — операции с дверью, встретившейся на пути. OnCombatRoundEnd — действия в конце раунда боя. OnConversation — действия после разговора (с игроком). OnDamaged — уточнение источника повреждения и, при необходимости, начало боя. OnDeath — действия для союзников убитого. OnDisturbed — проверка возможности окончания боя. OnHeartBeat — скрипт, повторяющийся каждые 6 секунд (например, крик рыночного зазывалы). OnPerception — скрипт запускается, если NPC видит или слышит игрока. OnPhysicalAttacked — запуск боя при прямой атаке (или, наоборот, игнорирование боя). OnRested — действия существа, которое попытается отдохнуть. Обычно пустой скрипт, но почему бы ему и не выпить какой-нибудь пузырек или сделать что-то еще. OnSpawn — скрипт, запускающий желательные для вас свойства существа в самом начале игры (один раз). Например, можно заставить его находиться в режиме Stealth. OnSpellCastAt — проверка, вредоносный спелл или нет, при положительном ответе объявляется бой. OnUserDefined — события “на вольную тему”. __ Территория (Area) OnEnter — скрипт, срабатывающий при попадании игрока на эту территорию. OnExit — скрипт при выходе игрока с территории.Дверь. OnAreaTransitionClick — скрипт, срабатывающий, если дверь служит для перехода с одной территории на другую. OnClose и OnOpen — действия с закрытой и открытой дверью. OnLock и OnUnlock — действия с запертой дверью (пока работают некорректно). OnFailToOpen — действия при провале попытки открыть дверь (команда монстрам за дверью, срабатывание ловушки и т.д.). Для двери также есть скрипты на всевозможные повреждения, но поскольку они не активируют бой, то можно повесить на них ловушки, срабатывающие при попытке разрушения двери. __ Локация (Encounter) Скрипты этого раздела отвечают за генерацию монстров в локации. Можно сделать их фиксированными, а можно и настроить под класс игроков. OnExhausted — скрипт, срабатывающий при гибели последнего монстра в локации. __ Объект (Placeable Object) Любая лежащая на полу вещь или контейнер. OnUsed — скрипт, срабатывающий при использовании вещи. __ Ловушка (Trap) OnDisarm — скрипт на обезвреживание ловушки. OnTrapTriggered — скрипт на срабатывание ловушки (особенно если ее вообще не обезвреживали). __ Триггеры К триггерам относятся также и ловушки, и точки перехода между территориями, но у них есть свои скрипты. Поэтому к триггерам лучше отнести точки, срабатывающие при вхождении на них игрока (OnClick). __ Модуль Это неповторимые скрипты, на которые тоже стоит обратить внимание. OnAcquireItem — скрипт на получение игроком некоторой вещи. OnActivateItem — использование игроком какой-то вещи. OnClientEnter — скрипт, связанный с вхождением игрока в модуль. OnClientLeave — при выходе игрока из модуля часть монстров может быть убрана (важно для многопользовательских модулей). OnModuleLoad — скрипт на первую загрузку модуля. OnPlayerDeath — скрипт при смерти игрока (может быть использован для изменения статистики игрока на сервере). OnPlayerDying — скрипт на нахождение игрока в бессознательном состоянии (от 0 до -10 хитов). OnPlayerRespawn — скрипт на оживление игрока после смерти (можно снижать или повышать характеристики и давать бонусы или пенальти). OnUnAcquireItem — скрипт на удаление из инвентаря игрока некоторой вещи (выполнение квеста). С местами помещения скриптов мы разобрались. Теперь дело за самими скриптами и их “грамматикой”. Типы данных Говорить о скриптах без упоминания о типах встречающихся в них данных невозможно. Поэтому сразу поясню, каковы они могут быть. Тип данных обозначается перед переменной или константой, которой нужно присвоить то или иное значение. То есть определить, для чего в дальнейшем будут нужны эти переменные, какую именно роль они будут выполнять. А вот и сами типы. Action — обозначение действия. Обычно применяется в функциях AnAction. Command — применяется для возвращения к void, обычно в функциях DelayCommand() и AssignCommand(). Constant — неизменяемое значение (обычно числовое). Effect — эффекты, происходящие с объектом. Например, в расположенном ниже скрипте у игрока происходит снижение уровня параметра Constitution на 1 на постоянной основе._effect eConPenalty = EffectAbilityDecrease(ABILITY_CONSTITUTION, 1); ApplyEffectToObject(DURATION_TYPE_PERMANENT, eConPenalty, oTargetPC); _ Event — активация любого события (например, атаки). Float — 32-битное число с плавающей точкой. Int — 32-битное целое число. Обычное обозначение любого числа. Location — координаты положения того или иного объекта. Object — выполнение действий с тем или иным объектом, назначение объектов для последующего выполнения операций с ними. String — текстовые данные, обычно имена. Struct — суммирование нескольких различных переменных или констант. Talent — служит для узнавания игроком тех или иных способностей существ (обычно лучших). Vector — данные для определения положения объекта (в трехмерном пространстве, по трем координатам). Void — обозначение начала скрипта. Константы Константы — это конкретно обозначенные эффекты и объекты, действие которых предусмотрено в редакторе. Так, есть значения DURATION_TYPE_INSTANT , DURATION_TYPE_PERMANENT и DURATION_TYPE_TEMPORARY. Они обозначают продолжительность эффектов, если эта самая длительность необходима вам для выполнения тех или иных скриптов. Первая константа — моментальное исполнение эффекта, вторая — эффект действует постоянно (например, звук действия продолжительного заклинания, пока оно не закончит действие или не будет рассеяно), третье — эффект ограничен по времени. Константы также нужны и для определения объектов, не имеющих конкретного tag (обозначения). Например, у вас есть задание для игрока, если тот является гномом. Но игрок не обязан начинать игру гномом. Поэтому определить это заранее невозможно. Тут-то вам и придут на помощь заранее на это настроенные константы. Достаточно сравнить расу игрока с расой, для которой предложен квест ( RACIAL_TYPE_GNOME ), и вы узнаете, подходит ли герой для выполнения задания или нет. Таким образом, константы — это заранее прописанные обозначения ( tag ) для заранее неизвестных объектов. Или стандарты для заранее известных. Функции То, что называется скриптовым языком NWN.
Скрипты в самом редакторе.
Операторы, выполняющие определенную работу. Состоят они обычно из самой функции, объекта действия функции и, если надо, конкретного действия по отношению к объекту. Хотя могут быть и варианты в виде полного отсутствия атрибутов (функция нужна для определения объекта) или нескольких дополнительных значений, как, например, у функции EffectDamage. Для нее могут быть указаны скорость эффекта (по умолчанию — мгновенная), тип эффекта (по умолчанию — константа DAMAGE_TYPE_MAGICAL ) и сила эффекта (по умолчанию DAMAGE_POWER_NORMAL ). Но лучше разобрать это на конкретных примерах. Но сначала все же закончим с правописанием скриптов. __ 1. Каждая новая часть скрипта начинается с открывающихся скобок и заканчивается скобками. Количество открытых скобок должно совпадать с количеством закрывающих. 2. Каждая строка скрипта заканчивается знаком " ; ", который показывает наступление окончания строки. Строка в редакторе может занимать больше одной полосы, поэтому для определения конца строки требуется знак " ; ". 3. Будет совсем не плохо, если между частями скрипта вы будете вставлять поясняющие вставки в виде //текст. Все, что стоит за этим знаком, игнорируется редактором и используется для комментариев данного куска скрипта. Это облегчит вам понимание того, что делает скрипт (и поможет разобраться, нет ли в нем ошибок). К сожалению, редактор не понимает русского языка, но никто не мешает вам оставлять комментарии транслитом. Ниже вы увидите пример использования этого действия. Разберем по частям один из скриптов. Он вставляется в слот OnOpen сундука и заставляет ближайшего стражника атаковать открывшего сундук.void main() { object oGuard = GetObjectByTag(“CHEST_GUARD”); object oPlayer = GetLastOpenedBy(); AssignCommand (oGuard, ActionDoCommand(SpeakString(“Taste my blade you bloody thief!”))); AssignCommand (oGuard, ActionAttack(oPlayer)); } Первая строка в данном скрипте — определение начала скрипта. Void main() — это указатель того, что началась основная часть скрипта, которую необходимо выполнить. Скобки в этом отрезке скрипта — для введения данных. Не скажу, что это понадобится вам сразу же, но стоит знать об этом. Вообще, в любые скобки можно ввести какие-то данные. Далее идут две строки, определяющие объекты действия скрипта. В первой строке выбирается свободный стражник из названных CHEST_GUARD (скрывающихся под таким tag). Именно ему присваивается значение объекта oGuard. Во второй строке определяется второй объект скрипта, которому будет присвоено значение oPlayer. Это тот, кто последний открыл сундук. Функция GetLastOpenedBy() в данном случае не требует никаких дополнительных атрибутов, так как присваивает значение oPlayer любому, попытавшемуся открыть сундук. Вы не обязаны присваивать именно такие имена объектам, но стоит обозначать объекты так, чтобы самим потом было понятно. То есть дверь лучше назвать oDoor , а не oPassage. Оригинальность — это хорошо, но потом сами не разберетесь, что хотели написать. Далее идут две строки с одинаковыми функциями AssignCommand. Эти команды требуют двух атрибутов: объекта, на который будет произведено воздействие, и самого воздействия (посредством другой функции). В первой строке объектом служит стражник, а действием — произнесение стражником угрожающей фразы. ActionDoCommand в данном скрипте выполняет только вспомогательную роль. Обычно он используется для построения скрипта с выстраиванием действий в правильную очередь. То есть в том случае, если персонажу нужно что-то сделать, потом что-то сказать, а потом опять сделать, вам нужна правильная очередность действий. Поскольку произнесение фразы — действие мгновенное, то оно произойдет раньше первого действия. ActionDoCommand задерживает произнесение фразы до того момента, когда первое действие будет выполнено. В этом скрипте нет необходимости в столь точном выполнении очередности, но лучше сразу привыкнуть к правильному написанию скриптов. Обратите также внимание на то, что фраза, произносимая стражником, находится в кавычках. Одних скобок мало, и, если не поставить кавычки, фраза работать не будет. Второй AssignCommand выполняет то действие, ради которого был создан скрипт. То есть назначает атаку для стражника. Объектом же атаки назначается персонаж, открывший сундук. Итак, что нужно обязательно вынести из этого примера: сначала назначаются объекты и переменные, используемые в скрипте, и только затем пишутся сами скриптовые команды. Всего в этом скрипте было использовано 5 функций. Но ведь есть и гораздо более сложные скрипты. Скрипты с логическими разветвлениями Разберемся с более сложными функциями. Рассмотрим скрипт, который некоторым напомнит стандартный цикл в языке Basic.void main() { object oDoor = GetNearestObject(OBJECT_TYPE_DOOR); int iDoorLocked = GetLocked(oDoor); ActionPlayAnimation(ANIMATION_PLACEABLE_ACTIVATE); if (iDoorLocked == TRUE) { AssignCommand(oDoor, ActionOpenDoor(oDoor)); SetLocked(oDoor, FALSE); } else { AssignCommand(oDoor, ActionCloseDoor(oDoor)); SetLocked(oDoor, TRUE); } } С объектом мы определились. Это дверь. Мы пытаемся ее открыть. Еще один объект — переменная, обозначающая, закрыта или открыта дверь. На открытие двери задается активация анимации открывания двери (которая сработает, если дверь откроется). А далее начинается цикл. Если ( if ) дверь закрыта, то поступает приказ дверь открыть и командой SetLocked дверь далее считается открытой. Если же дверь открыта, то она закрывается и далее считается закрытой. Мудрено? Да. Но попытайтесь приладить этот скрипт к месту и посмотреть его в действии. И поймете, что это не так сложно. Или другой скрипт, в котором тоже есть цикл проверки.object oPlayer=GetLastPerceived(); object oItem=GetItemPossessedBy(oPlayer, “SWORD”); object oDoor=GetObjectByTag(“OPEN_DOOR”); object oKey=GetObjectByTag(“KEY”); void main() { //Если проверка удовлетворена, то скрипт продолжает выполняться дальше. // Если нет, то перескакивает на ELSE._if (GetIsPC(oPlayer) &&GetLastPerceptionSeen()) { if (oItem==OBJECT_INVALID) { _ //Дает ключ игроку. ActionGiveItem (oKey, oPlayer); } // Открывает дверь.
Названия переменных. И у вас столько будет.
else { AssignCommand (oDoor, ActionOpenDoor(oDoor)); } } } Обратите внимание, что присваивать имена объектам можно как начав скрипт, так и перед началом основной части скрипта. Этот скрипт помещается в oPerceived слот NPC. Смысл скрипта в том, что если у игрока нет меча, то ему дают ключ. Если же есть меч, то сразу открывают дверь. Итак, oPlayer — “последний” замеченный NPC персонаж, oItem — вещь, которой владеет oPlayer , а дверь и ключ — соответственно, заранее обозначенные объекты. Далее в начале скрипта происходит проверка, является ли объект объектом для скрипта. То есть мы узнаем, является ли игрок именно PC и действительно ли он тот, кто последний привиделся NPC. Причем функция GetLastPerceptionSeen() нужна для того, чтобы скрипт срабатывал только на тех, кого УВИДЕЛИ. Потому как слот работает еще и на тех, кого услышат. Таким образом, скрипт не действителен для игрока, прокравшегося мимо NPC. Его, возможно, и услышали, но точно не увидели. Действительной эта проверка явится, только если верны обе части запроса. А для того, чтобы обеспечить это равноправие, служит & &, заменитель логического оператора AND. Если проверка не верна, то скрипт прекращает свою работу. Если верна — начинается вторая проверка. В ней проверяется, есть ли ключ у персонажа. Проверка производится отрицанием. То есть, если ключа у персонажа нет (константа OBJECT_INVALID ), то проверка считается не пройденной. Обратите внимание на двойной знак " = ". Это знак проверки переменных и объектов, являющихся как бы запросом “а действительно ли одна часть скрипта абсолютно равна второй”. Таким образом, если меча у игрока нет, то ему выдается ключ. Если же он есть — открывается дверь.
В этой статье достаточно много сказано о скриптах в редакторе NWN , но еще больше не сказано. Да и можно ли уместить все на нескольких журнальных страницах, когда редактор насчитывает сотни функций и констант! Поэтому для досконального изучения его работы рекомендую сделать несколько действий: 1. прочитать эту статью; 2. воспользоваться помощью Help-файла NWN Lexicon ; 3. просмотреть как можно большее количество скриптов “по умолчанию” в самой игре; 4. достать несколько модулей и посмотреть, как реализованы авторские скрипты там. Не забывайте, что игра сама подсказывает, как нужно писать тот или иной скрипт (поле внизу редактора), показывает необходимые атрибуты той или иной функции. Но, конечно, для хорошего освоения этого редактора необходимо большое количество работы с ним для приобретения опыта. С ним вы получите новые уровни в классе script-builder и станете уважаемы простыми игроками, которые оценят по достоинству ваши яркие и красочные карты. А напоследок — еще один интересный скрипт, над которым вам предстоит самим подумать: куда его поставить и что он делает. Удачи! void main() { ClearAllActions(); ActionPlayAnimation(ANIMATION_FIREFORGET_DRINK); ActionWait(1.5); ActionSpeakString(“I am drink vodka… la-la.”); }