Игроделу | Триггеры | Neverwinter Nights Основной Переход Местности Письмена Улучшенный
По сайту
Главная
Рыцарская сага
Файлы
Neverwinter Nights
Гостевая книга
Форум сайта

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


Внимание! Рисунок интерактивный, можно кликать по обведенным секторам и зеленым кнопкам.

  1. OnClick – событие при клике мышкой
  2. OnEnter – событие при входе
  3. OnExit – событие при выходе
  4. OnHeartbeat – циклическое событие т.е. через 6 секунд
  5. OnUserDefined – событие при подаче сигнала

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

  1. Инициировать секретный объект
  2. Ловушки
  3. Переход Местности
  4. Триггер генерации

Все эти триггеры можно удалить скриптом с функцией DestroyObject, в отличие от триггера встреч. Что еще объединяет эти все виды триггеров? Это панель “Письмена”:

Как начертить триггер? Очень просто, кликаем в палитре на нужный, появляется курсор-кисточка. Последующий клик по локации определить начальную точку рисования, отведя кисточку в сторону, мы увидим тянущеюся за ней линию. Кликнув еще раз, получим отрезок, ну и так далее пока не очертим нужный контур. Последний двойной клик замкнет контур. Число точек и конфигурация может быть разная. Теперь разберем сами триггеры, и чем они отличаются…

Триггер - Инициировать секретный объект

Как это все сделать? (Вообще это обычный триггер генерации, только с определенными скриптами вызова из палитры секретных объектов.) Выберите необходимый триггер, допустим “Секретная Дверь Деревянная”, начертите триггер. А теперь кликните свойства этого триггера, и откройте панель “Письмена”. В слоте мы увидим скрипт “x0_o2_sec_door1”, откроем его и скопируем стринг x0_sec_door1 из функции:

RevealSecretItem("x0_sec_door1");

Это будет наш “ResRef” секретной деревянной двери (можете проверить в соответствующей палитре). Его мы ставим в строку имени триггера на панели “Основной”. Понятно, что дверь должна куда-то вести, нам нужна точка перемещения. Определим ее, на секретной двери уже весит скрипт на перемещение, нам остается только правильно записать тэг точки. Так вот, все точки перемещения секретных объектов имеют начало тэга “DST_”, а продолжение привязка к нашему триггеру, т.е. его тэг. Допустим тэг триггера у нас TRIGGER_1 тогда точка перемещения для двери будет:

DST_ TRIGGER_1

Аналогично нам нужна и точка появления двери, она будет LOC_+Тэг триггера, и в итоге мы получим:

LOC_ TRIGGER_1

Вот собственно и все хитрости, а вот как это можно записать для памятки:

// Секретный объект:
// Триггер содержит в названии ResRef объкта, который появится.
// Точка, где появится объект LOC_+Тэг триггера.
// Тег точки, куда перемещаться DST_+Тег триггера.

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

//:://////////////////////////////////////////////
//:: Created By: Gennady    СЕКРЕТНЫЙ ТРИГГЕР
//:: Created On:30.03.2006  Name: sec_ object _1
//:://////////////////////////////////////////////
#include "nw_i0_tool"
void SecObekt(location Loc, object oPC)
{
 string sResRef = GetName(OBJECT_SELF); // Объект создаваемый
 string sNewTag = GetTag(OBJECT_SELF);
 effect eEffect = EffectVisualEffect(VFX_IMP_DISPEL);
 ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eEffect, Loc);
 AssignCommand(oPC, ClearAllActions(TRUE));
 AssignCommand(oPC,ActionDoCommand(SetFacingPoint(GetPositionFromLocation(Loc))));
 SetLocalInt(OBJECT_SELF, "DEACTIVATED", TRUE);
 DelayCommand(2.0,AssignCommand(oPC,PlayVoiceChat(VOICE_CHAT_LOOKHERE)));
 CreateObject(OBJECT_TYPE_PLACEABLE, sResRef, Loc, TRUE, sNewTag);
 DestroyObject(OBJECT_SELF, 10.0);
}
void main()
{
  object oPC = GetEnteringObject();
  object oSP = GetHenchman(oPC);
  object oSelf = OBJECT_SELF;
  if(!GetIsPC(oPC)||GetIsInCombat(oPC)||GetLocalInt(oSelf,"DEACTIVATED")) return;
  object oWP = GetNearestObjectByTag("LOC_"+GetTag(oSelf));// Если конкретное место
  location Loc = GetLocation(oSelf);
  int iDC = StringToInt(GetLockKeyTag(oSelf)); // Сложность проверки скилла
  int nSkillZ = 20 + GetSkillRank(SKILL_OPEN_LOCK, oPC);
  int nSkillZsp = 20 + GetSkillRank(SKILL_OPEN_LOCK, oSP);
  string sDCZ = GetLockKeyTag(oSelf);
  float fSkilloPC = IntToFloat(GetSkillRank(SKILL_OPEN_LOCK, oPC));
  int iSkilloPC = FloatToInt(fSkilloPC);
  string sDCoPC = " (20+"+IntToString(iSkilloPC)+")";
  string sSUM = IntToString(iSkilloPC+20);
  float fSkilloSP = IntToFloat(GetSkillRank(SKILL_OPEN_LOCK, oSP));
  int iSkilloSP = FloatToInt(fSkilloSP);
  string sDCoSP = " (20+"+IntToString(iSkilloSP)+")";
  string sSUMS = IntToString(iSkilloSP+20);

if (GetIsObjectValid(oWP))
Loc = GetLocation(oWP);
if(iDC > 3)
{
 if (iDC <= nSkillZ)
 {
  SecObekt(Loc, oPC);
  if (GetGender(oPC)==GENDER_MALE)
  AssignCommand(oPC, SpeakString("<c °у>О! Я что-то нашел!</c>"));
  else
  AssignCommand(oPC, SpeakString("<c °у>О! Я что-то нашла!</c>"));
  SendMessageToPC(oPC, "<c °у>Бросок(Open Lock): </c>"+sDCoPC+"= "+sSUM+
  "<c °у> против Класса Сложности = </c>"+sDCZ);
  return;
 }
 else
 {
  SendMessageToPC(oPC, "<cу  >ВЫ НЕ МОЖЕТЕ ОТКРЫТЬ ПОТАЙНОЙ ЗАМОК</c>");
  SendMessageToPC(oPC, "<c °у>Бросок(Open Lock): </c>"+sDCoPC+"= "+sSUM+
  "<c °у> против Класса Сложности = </c>"+sDCZ);
  AssignCommand(oPC, SpeakString("<c у >Здесь что-то есть!</c>"));
  DelayCommand(2.0, AssignCommand(oPC, PlayVoiceChat(VOICE_CHAT_SEARCH)));
  if(GetIsObjectValid(oSP) && iDC<=nSkillZsp && GetDistanceBetween(oPC,oSP)<5.0)
  {
   SecObekt(Loc, oPC);
   AssignCommand(oSP, ClearAllActions());
   AssignCommand(oSP, ActionMoveToObject(oSelf, TRUE));
   AssignCommand(oSP, ActionPlayAnimation(ANIMATION_LOOPING_GET_LOW, 1.0, 3.0));
   DelayCommand(0.1, SetCommandable(FALSE, oSP));
   DelayCommand(5.0, SetCommandable(TRUE, oSP));
   AssignCommand(oSP, SpeakString("<c °у>Я тебе помогу!</c>"));
   SendMessageToPC(oPC, "<c °у>Бросок(Open Lock): </c>"+sDCoSP+"= "+sSUMS+
   "<c °у> против Класса Сложности = </c>"+sDCZ);
  }
  else
   AssignCommand(oSP, SpeakString("<c °у>Извини! Я не могу тебе помочь...</c>"));
  return;
  }
}
else
{
 if(AutoDC(iDC, SKILL_SEARCH, oPC) && !GetLocalInt(oSelf, "DEACTIVATED"))
  {
   SecObekt(Loc, oPC);
   if (GetGender(oPC)==GENDER_MALE)
   AssignCommand(oPC, SpeakString("<c °у>О! Я что-то нашел!</c>"));
   else
   AssignCommand(oPC, SpeakString("<c °у>О! Я что-то нашла!</c>"));
  }
 else
  {
   if (GetDetectMode(oPC) == DETECT_MODE_PASSIVE)
   SendMessageToPC(oPC, "<cу  >ВКЛЮЧИТЕ РЕЖИМ ОБНАРУЖЕНИЯ</c>");
   AssignCommand(oPC, SpeakString("<c у >Здесь что-то есть!</c>"));
   DelayCommand(2.0, AssignCommand(oPC, PlayVoiceChat(VOICE_CHAT_SEARCH)));
  }
 }
}
/*            Секретный объект
  Триггер содержит в названии ResRef объкта, который появится.
  Точка, где появится объект LOC_+Тег триггера, если точки нет - то к триггеру.
  Тег точки, перемещения  DST_+Тег триггера.
  Поле триггера KeyTag - сложность проверки скилла:
  DC_EASY = 0; DC_MEDIUM = 1; DC_HARD = 2;
  Если в поле цифра больше 3, то идет проверка на открывание замка.
  Цифра будет величиной DC на открывание.
*/

Триггер - Ловушки

Обычное меню настройки ловушки, более подробно описано в разделе “Двери”. Как и на дверь, на триггер можно повесить скрипт получения опыта за обезвреживание ловушки. Не знаю зачем, но многие пытаются открыть ловушки скриптами… Если и вам это надо, вот тогда функция:

SetTrapDetectedBy(oLovuska, oPC); // обозначить ловушку

Триггер - Переход Местности

Это важный триггер для перехода в другие локации. У него есть специальная панель, и помощник для построения перехода, кнопка “Настройка перехода между областями”.

Обычно с настройкой простого перехода не вызывает ни у кого проблем, более подробно настройка рассматривалось в разделе “Двери”. Лучше поговорим о тонкостях… Стоит знать, что если просто поставить галку “Точка маршрута” или “Дверь”, то у же на нашем триггере появится курсор перехода местности, при наведение указателя мыши. Если теперь поставить универсальный скрипт перемещения в слот, то у нас автоматом получится переход только для героя, т.к. монстры не кликают по объектам, при этом мы оставим прежний цвет окраски триггера. Вот мой универсальный скрипт для такого случая и практически для всех других:

//:://////////////////////////////////////////////
//:: УНИВЕРСАЛЬНЫЙ СКРИПТ ПЕРЕМЕЩЕНИЙ
//:://////////////////////////////////////////////

// *****  ПЕРЕМЕЩЕНИЕ ГЕРОЯ *****
void JumpFirstPC(object oWP)
{
 object oPC = GetFirstPC();
 object oNPC = GetFirstObjectInArea(oPC);

   AssignCommand(oPC, ClearAllActions());
   AssignCommand(oPC, JumpToObject(oWP));
 while (GetIsObjectValid(oNPC))
 {
  if (oPC==GetMaster(oNPC))
  {
   AssignCommand(oNPC, ClearAllActions());
   AssignCommand(oNPC, JumpToObject(oWP));
  }
  oNPC = GetNextObjectInArea(oPC);
 }
}
// *********************************
void main()
{
 object oSelf = OBJECT_SELF;
 object oPoint = GetWaypointByTag("WP_" + GetTag(oSelf));
 object oPC = GetFirstPC();
 location lSmoke = GetLocation(oSelf);
 effect eSmoke = EffectVisualEffect(VFX_FNF_LOS_NORMAL_10);

if ((GetArea(oPC) == GetArea(oSelf)&& GetDistanceBetween(oPC, oSelf) < 3.0) ||
     GetIsPC(GetEnteringObject()))
  {
   ApplyEffectAtLocation(DURATION_TYPE_INSTANT,eSmoke,lSmoke);
   DelayCommand(0.6, JumpFirstPC(oPoint));
  }
 else
 AssignCommand(GetEnteringObject(), ClearAllActions());
}
 // помещать в слот OnUsed OnEnter OnClick OnAreaTransitionClick
 // Точка перемещения содержит WP_+Тег объекта

Бывает еще нужно сделать авто сохранение перед заходом в опасную область, тогда вам поможет подобный скрипт (только функцию перемещения героя JumpFirstPC я не буду повторно копировать, ее лучше вообще положить куда-либо в инклюду и компилировать скрипт с подключением этой инклюды). Эти два скрипта также хорошо работают на переходах дверей и выходов в другие области, как впрочем, и переходах внутри одной области. Сам скрипт:

//:://////////////////////////////////////////////
//:: УНИВЕРСАЛЬНЫЙ СКРИПТ ПЕРЕМЕЩЕНИЙ
//::  Авто сохранение
//:://////////////////////////////////////////////
#include "******"
void main()
{
 object oSelf = OBJECT_SELF;
 object oPoint = GetWaypointByTag("WP_" + GetTag(oSelf));
 object oPC = GetFirstPC();
 if ((GetArea(oPC) == GetArea(oSelf)&&GetDistanceBetween(oPC, oSelf) < 3.0) ||
      GetIsPC(GetEnteringObject()))
  {
   if(GetLocalInt(oSelf, "OPEN_SD") == 1)
    JumpFirstPC(oPoint);
   else
   {
    SetLocalInt(oSelf,"OPEN_SD",1);
    DoSinglePlayerAutoSave();
    DelayCommand(60.0,SetLocalInt(oSelf,"OPEN_SD",0));
   }
  }
 else
 AssignCommand(GetEnteringObject(), ClearAllActions());
}
  // помещать в слот OnUsed OnEnter OnClick OnAreaTransitionClick
  // Точка перемещения содержит WP_+Тег объекта

Триггеры генерации

Это самые простые и самые используемые. Использование может быть самое разнообразно, как-то вызов монстров (см. раздел “Встречи”), просто фразы или переход местности…

“Показать высоту” - можно проставить в тех случаях, когда вы хотите увидеть окраску триггера не только в виде плоскости, но и окрасить объекты, находящиеся в триггере. Стоит знать, что триггер начинается с нулевой, или самой низкой точки локации, и идет до бесконечности вверх. При этом угол с наименьшим значением, является точкой локации триггера, а высота локации, самая низкая координата области… Хотя, может я и ошибаюсь… но вызов плейсов и существ к локации триггера, идет именно к самому острому углу. Если поставить в слот простого триггера генерации скрипт, то тогда триггер станет видимым, и его цвет будет голубым, в отличие от фиолетового для перехода, и красного для ловушки. Такой триггер уже будет реагировать на клик мышки.

В своих скриптах я использую тэг ключа для записи некоторых проверок. На более новых версия редактора Neverwinter Nights, чем 1.32, для это служит панель записи локальных переменных на объекты… Но тем и хорош редактор, что позволят даже на один тэг навесить сотни переменных… Вот скрипт для текста над головой героя при наступление на триггер, причем если нужно, то можно в поле ключа пробить локалку на такое событие:

//:://////////////////////////////////////////////
//::  ON ENTER ТРИГГЕРА
//:: File name: trigger_text_pc
//:://////////////////////////////////////////////
void main()
{
 object oPC = GetEnteringObject();
 object oSelf = OBJECT_SELF;
 string sStr = GetName(oSelf);
 string sPer = GetLockKeyTag(oSelf);
 int iPer = StringToInt(sPer);
 int iNN = StringToInt(GetTag(oSelf)); // Количество всплываний текста

 if (!GetIsPC(oPC)) return;
 if (GetLockKeyTag(oSelf) == "")
     DestroyObject(oSelf, 0.3);
 else
   {
    if (iPer < 1)
     SetLocalInt(GetFirstPC(), sPer, 1);
   }
  if (iNN < 1) {AssignCommand(oPC, SpeakString(sStr)); return;}
  if (GetLocalInt(oSelf, "POVTOR") == 1) return; // Проверить
  SetLocalInt(oSelf, "POVTOR",1);
  DelayCommand(60.0, SetLocalInt(oSelf, "POVTOR",0));
  SetLocalInt(oSelf, "NUM", GetLocalInt(oSelf, "NUM") + 1);
  if (GetLocalInt(oSelf, "NUM") >= iNN)
  DestroyObject(oSelf, 0.3);
  AssignCommand(oPC, SpeakString(sStr));
}
/*  Триггер берет текст из своего имени, и герой произносит его
    Если есть запись в поле KEY TAG, то повтор текста
    Если запись не цифра, то присвоить переменную герою с тегом ключа
    Если тег цифра, то это количество всплываний текста 
*/

Бывает необходимость привлечь внимание игрока к определенному персонажу, тогда вам поможет такой небольшой универсальный скрипт подхода на вход триггера, и старт диалога героя с НПС. Нужно очертить триггер вокруг НПС, и проставить в поле ключа триггера, тэг нашего НПС… Сам скрипт:

//:://////////////////////////////////////////////////
//:: Герой подходит к НПС и начинает диалог
//:: File name: camera_dialog
//:://////////////////////////////////////////////////
void main()
{
   object oPC = GetEnteringObject();
   object oSP = GetHenchman(oPC);
   vector vFace = GetPosition(oPC);
   string sName = GetLockKeyTag(OBJECT_SELF);
   int i = 1;
   float fTime;
   object oDL = GetNearestObjectByTag(sName, oPC, i);
   int iA = GetGoodEvilValue(oPC);

 if (!GetIsPC(oPC)) return;
 while (GetIsObjectValid(oDL))
 {
  if (!GetIsInCombat(oPC)&&!GetIsInCombat(oDL)&&!GetIsEnemy(oPC, oDL)&&
      !GetIsDead(oDL) && GetObjectType(oDL) == OBJECT_TYPE_CREATURE)
   {
    fTime = GetDistanceBetween(oPC, oDL);
    SetLocalInt(OBJECT_SELF,"VALID",1);
    break;
   }
   i++;
  oDL = GetNearestObjectByTag(sName, oPC, i);
 }
 if (GetLocalInt(OBJECT_SELF, "VALID") != 1) return; // Проверить
 if(fTime < 5.0) fTime = 7.5;
 string sPC = "Заткнись! Если что не то - порву!";
 if (iA>30 && iA<70) sPC = "Не ори! Иду, иду...";
 if (iA>=70 && iA<100) sPC = "Уже иду... Что случилось?";
 if (iA==100) sPC = "Уже иду! Что там у вас случилось?";

AssignCommand(oDL, ClearAllActions());
AssignCommand(oDL, SetFacingPoint(vFace));
AssignCommand(oDL, ActionPlayAnimation(ANIMATION_LOOPING_PAUSE, 1.0, 1.5));
AssignCommand(oDL, ActionPlayAnimation(ANIMATION_FIREFORGET_GREETING));
AssignCommand(oDL, ActionWait(fTime-3.0));
AssignCommand(oDL, ActionStartConversation(oPC)); // диалог персонажа
AssignCommand(oDL, ActionDoCommand(SetCommandable(TRUE, oDL))); 
DelayCommand(0.1, SetCommandable(FALSE, oDL)); // заблокировать очередь
DelayCommand(0.2, SetCutsceneMode(oPC, TRUE));
DelayCommand(0.3, AssignCommand(oPC, ClearAllActions(TRUE)));
DelayCommand(0.4, AssignCommand(oSP, ClearAllActions(TRUE)));
DelayCommand(1.5, AssignCommand(oPC, 
ActionForceMoveToObject(oDL, FALSE, 1.0, fTime-2.0)));
DelayCommand(1.5, AssignCommand(oSP, 
ActionForceMoveToObject(oPC, FALSE, 1.0, fTime-2.0)));
DelayCommand(2.0, AssignCommand(oDL, 
SpeakString("<c у >Подойдите ко мне! Это очень важно!</c>")));
DelayCommand(fTime-3.5, AssignCommand(oPC, SpeakString(sPC)));
DelayCommand(fTime, SetCutsceneMode(oPC, FALSE));
DelayCommand(fTime, SetCommandable(TRUE, oDL)); // разблокировать очередь
// диалог персонажа
DelayCommand(fTime+0.1, AssignCommand(oPC, ActionStartConversation(oDL))); 
DestroyObject(OBJECT_SELF, fTime+1.0);
}

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

void main()
{
 object oPCG = GetObjectByTag("T_BOP_1");
 object oPCG1 = GetObjectByTag("T_BOP_2");
 object oZvuk = GetNearestObjectByTag("ZVUK_TRIGER");
 object oPC = GetEnteringObject();
if (GetIsPC(oPC) && GetLocalInt(OBJECT_SELF,"DIALOG")!=1)
{
 SetLocalInt(OBJECT_SELF,"DIALOG", 1);
 DelayCommand(60.0, SetLocalInt(OBJECT_SELF,"DIALOG", FALSE));
 AssignCommand(oPCG1, ClearAllActions());
 AssignCommand(oPCG1, SetFacingPoint(GetPosition(oPCG)));
 DelayCommand(1.5, AssignCommand(oPCG, SpeakString
  ("Ну что у вас тут интересного слышно?")));
 DelayCommand(1.5, AssignCommand(oZvuk, PlaySound("vs_nwncomm4_say")));

 DelayCommand(5.5, AssignCommand(oPCG1, SpeakString
   ("Я подсмотрел вчера за Теневыми Мастерами...")));
 DelayCommand(5.5, AssignCommand(oZvuk, PlaySound("vs_nthugxm3_say")));
 DelayCommand(5.5, AssignCommand(oPCG1,
     ActionPlayAnimation(ANIMATION_LOOPING_LOOK_FAR,1.0)));

 DelayCommand(10.5, AssignCommand(oPCG, SpeakString
   ("Давай, по одной и говори, что ты видел вчера.")));
 DelayCommand(10.5, AssignCommand(oZvuk, PlaySound("vs_nwncomm4_yes")));
}
}
Обновление: Rambler's Top100