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

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


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

  • OnAcquireItem – Когда герой получит новый предмет
  • OnActivateItem – При активации предмета
  • OnClientEnter – При вхождение нового героя в модуль
  • OnClientLeave – Для шарда …
  • OnCutsceneAbort – По окончанию кат сценки
  • OnHeartbeat – Цикл, работает каждый раунд т.е. 6 секунд
  • OnModulLoad – Каждый раз при загрузке модуля
  • OnPlayerDeath – Когда герой умрет
  • OnPlayerDying – Собственно, когда умирает…
  • OnPlayerLevelUp – Когда герой повышает уровень
  • OnPlayerRespawn – Когда жмем кнопку воскресить героя
  • OnPlayerRest – Когда жмем кнопку “Отдых”
  • OnUnAcquireItem – Когда герой теряет предмет
  • OnUserDedined – Когда посылаем сигнал модулю
  • Модуль

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

    Панель “Основной” служит для внесения названия вашего модуля и придание модулю оригинального тэга. Панель “Описание” выводит текст, при выборе и загрузке модуля: “Новая игра” => “Другие модули”… Панель “Улучшенный” служит для определения стартового дня и часа игры. Также тут можно задать опыт за уничтожение монстров, и выбрать длительность игрового часа, обычно час равен двум минутам реального времени. Панель “Настройки пользователя” позволит подключить к модулю дополнительный ваш хак, лежащий в папке “hak”. Основной интерес для игродела представляет панель “События”, где посредством скриптов мы можем более индивидуально настроить игру. Поэтому раздерем более подробно, что тут к чему…

    Начнем, наверное, со слота “OnUnAcquireItem”, который реагирует на потерю предметов инвентаря персонажей. Это может пригодится при создание какой-либо сюжетной вещицы, которую герой не должен потерять, а главное в этом слоте мы можем отследить продажу вещи в магазин. Для чего? Просто магазины Невера, если там нет для продажи вещи, с неограниченным количеством продаж, не складывают одинаковые вещи, поэтому это не только не красиво, но и тормозит сам процесс купли-продажи… Это можно поправить добавил в события модуля два скрипта. Итак, скрипт в слот “OnUnAcquireItem”:

    //:://///////////////////////
    //:: СЛОТ: OnUnAcquireItem
    //:://///////////////////////
    void main()
    {
     object oPC = GetModuleItemLostBy();
     object oLostItem = GetModuleItemLost();
     object oMag = GetItemPossessor(oLostItem);
     object oItem = GetFirstItemInInventory(oMag);
     int iBase = GetBaseItemType(oLostItem);
     int iNum;
    
    // *****  ДЛЯ МАГАЗИНА  *****
     if (GetObjectType(oMag) == OBJECT_TYPE_STORE)
      {
        while (GetIsObjectValid(oItem))
          {
           if (GetTag(oLostItem) == GetTag(oItem)) iNum++;
            if (iNum > 1)
             {
               int iSt1 = GetItemStackSize(oLostItem);
               int iSt2 = GetItemStackSize(oItem);
               int iStack = iSt1+iSt2;
               int iKol = 10;
                  if(iBase == BASE_ITEM_BOLT ||
                     iBase == BASE_ITEM_BULLET ||
                     iBase == BASE_ITEM_ARROW)
                  iKol = 99;
                  if(iBase == BASE_ITEM_DART ||
                     iBase == BASE_ITEM_THROWINGAXE ||
                     iBase == BASE_ITEM_SHURIKEN)
                  iKol = 50;
               SetItemStackSize(oItem, iStack);
               if(GetItemStackSize(oItem)==1)
               {
                 string sTag = GetTag(oItem);
                 SetLocalInt(oMag, sTag, GetLocalInt(oMag, sTag) + 1);//Прибавить
               }
               if(iStack > iKol)
                  {
                   oItem = CopyObject(oLostItem, GetLocation(oPC), oMag);
                   SetItemStackSize(oItem, iStack-iKol);
                  }
               DestroyObject(oLostItem);
               break;
             }
           oItem = GetNextItemInInventory(oMag);
          }
      }
    
    // *****  ДЛЯ СЮЖЕТНОГО ПРЕДМЕТА  *****
     if (GetTag(oLostItem)=="TAG")
      {
       FloatingTextStringOnCreature("Вы не можете выложить сюжетный предмет.", oPC);
       CopyItem(oLostItem, oPC);
       DestroyObject(oLostItem);
      }
    }

    Мы складываем вещи со стеком более единицы, а одинарные вещи отмечаем локалкой на магазине с тегом этой вещи. Если таких вещей много, то мы просто увеличиваем значение присвоенной ранее локалки. Теперь при покупке в магазине вещей, имеющих количественную локалку, мы снимем с этой локалки единичку, т.к. в магазине останется на одну вещь меньше. Это у нас будет скрипт на слот “OnAcquireItem” приобретения вещи персонажем. Этот же слот служит для проверки получения квестовой вещи, и записи события в журнал (см. Редактор журнала). Скрипт для магазина:

    //:://///////////////////////
    //:: СЛОТ: OnAcquireItem 
    //:://///////////////////////
    void main()
    {
        object oPC = GetFirstPC();
        object oMod = GetModule();
        object oItem = GetModuleItemAcquired();
        object oFrom = GetModuleItemAcquiredFrom();
        object oMag = GetNearestObject(OBJECT_TYPE_STORE, oPC);
        string sTag = GetTag(oItem);
    
    //******************** ДЛЯ МАГАЗИНА *****************************
     if(oMag == oFrom)
     {
      int iNew =  GetLocalInt(oMag, sTag);
      if(iNew > 0)
      {
       CopyItem(oItem, oMag);
       SetLocalInt(oMag, sTag, iNew - 1);
      }
     }
    //*************************************************
    // Журнальные записи по квестовым предметам
    //*************************************************
    //------------ Шкура тигра ------------------------
     if(sTag == "ITEMM_PASTUX")
     {
      if(GetLocalInt(oMod, sTag) == 0 && GetItemPossessor(oItem) == oPC)
      {
       AddJournalQuestEntry("Z_PASTUX", 2,oPC); // журнал
       SetLocalInt(oMod, sTag,1);
      }
     }
    }

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

    //:://///////////////////////
    //:: СЛОТ: OnClientEnter
    //:://///////////////////////
    void main()
    {
     object oPC = GetEnteringObject();
     if (oPC == GetFirstPC())
       {
        object oMod = GetModule();
        int i;
        object oItem;
        AssignCommand(oPC, SetCameraMode(oPC, CAMERA_MODE_TOP_DOWN));
     if (GetLocalInt(GetModule(), "InitGame") != 1)
        {
         SetXP(oPC, 1000); // Дать опыт
         SetLocalInt(GetModule(), "InitGame", 1);
         object oInv = GetFirstItemInInventory(oPC);
         while(GetIsObjectValid(oInv))
          {
           DestroyObject(oInv);
           oInv = GetNextItemInInventory(oPC);
          }
         for (i = 0; i < NUM_INVENTORY_SLOTS; i++)
          {
           oItem = GetItemInSlot(i, oPC);
           if (GetIsObjectValid(oItem))
           DestroyObject(oItem);
          }
         if(GetGold(oPC) > 1000)
          {
           // забрать все золото
           AssignCommand(oPC, TakeGoldFromCreature(GetGold(oPC), oPC, TRUE));
           DelayCommand(1.0, GiveGoldToCreature(oPC, 100)); // Даем золото
          }
          else
           DelayCommand(1.0, GiveGoldToCreature(oPC, 100)); // Даем золото
    
          // ***** Оденем *****
       object oMan = CreateItemOnObject("nw_mcloth006", oPC, 1); // Роба
       object oPem = CreateItemOnObject("nw_it_mbelt016", oPC, 1); // Ремень
       object oZep = CreateItemOnObject("nw_wdbmqs002", oPC, 1); // Посох
    
       object oItemCr = GetFirstItemInInventory(oPC);
         while(GetIsObjectValid(oItemCr))
          {
           SetIdentified(oItemCr,TRUE);
           oItemCr = GetNextItemInInventory(oPC);
          }
      DelayCommand(2.0,AssignCommand(oPC,ClearAllActions()));
      DelayCommand(2.1,AssignCommand(oPC,ActionEquipItem(oMan,INVENTORY_SLOT_CHEST)));
      DelayCommand(2.11,AssignCommand(oPC,ActionEquipItem(oPem,INVENTORY_SLOT_BELT)));
      DelayCommand(2.12,AssignCommand(oPC,ActionEquipItem(oZep,INVENTORY_SLOT_RIGHTHAND)));
     }
     }
    }

    Рассмотрим еще пример скрипта на слот “OnActivateItem”. Этот слот служит для создания уникальных вещей, которые обычным настроем палитры невозможно сделать. Рассмотрим для примера кольцо возвращение в определенную закрытую локацию, и последующего возвращения в место первоначальной активации:

    //:://///////////////////////
    //:: СЛОТ: OnActivateItem
    //:://///////////////////////
    void main()
    {
    object oActivator = GetItemActivator(); // это активатор объекта(наш PC)
    object oActivated = GetItemActivated(); // это активируемый объект
    string sTag = GetTag(oActivated);
    
    //*****  КОЛЬЦО ПРЕМЕЩЕНИЯ  ******
    if (sTag == "KOL_PER")
    {
      object oModule = GetModule();
       // Точка в закрытой локации
      object oPoint = GetWaypointByTag("WP_PER" );
      location Loc = GetLocalLocation(oModule, "Return_LOC_1");
      DelayCommand(3.0, AssignCommand(oActivator, ClearAllActions(TRUE)));
     if (GetLocalInt(oActivator, "peremez_1") == 0)
      {
       DelayCommand(3.1,AssignCommand(oActivator,ActionJumpToObject(oPoint)));
       //  запоминаем координаты игрока перед прыжком
       SetLocalLocation(oModule, "Return_LOC_1", GetLocation(oActivator));
       SetLocalInt(oActivator, "peremez_1", 1);
      }
      else
      {
       DelayCommand(3.1, AssignCommand(oActivator, ActionJumpToLocation(Loc)));
       SetLocalInt(oActivator, "peremez_1", 0);
      }
      effect eSmoke = EffectVisualEffect(VFX_FNF_DISPEL);
      location lSmoke = GetLocation(oActivator);
      ApplyEffectAtLocation(DURATION_TYPE_INSTANT,eSmoke,lSmoke);
     }
     return;
    }

    Работу всех остальных скриптов нет резона рассматривать. Это уж ваша прерогатива построения модуля. Схем и их взаимодействия, может быть множество, советую посетить сайт “WRG.ru”, где многие игроделы поделились своими идеями и схемами, или скачать наш модуль и разобраться с тем, что мы там навертели…

    Свойства области - Neverwinter Nights

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

    Область

    При создании области стоит обратить внимание на ее ResRef. У нас получается, что при создании новой области, ей присваивается порядковый ResRef. Если вы предпочитаете делать область в маленьком (испытательном) модуле, а затем переносить в основной, то переносимая область, должна иметь свой уникальный ResRef. Как его сделать? Очень просто – сделайте копию, и при создании укажите свой ResRef.

    В свойствах создаваемой области есть еще панель – “Звук”. То есть та музыка и звуковое сопровождение, которое будет звучать каждый раз, когда герой войдет в эту локацию. Редактор, автоматически дает стандартный вариант для дневного и ночного звукового сопровождения, боев и окружающие (атмосферные звуки). Можно также отрегулировать уровень громкости, передвинув ползунок в нужное положение. По умолчанию Звуковые Эффекты имеют базовую настройку, и это можно поменять на те звуковые эффекты, которые нужны именно для вашей области. Иногда музыка в локации не нужна вовсе, но правильно подобранное звуковое оформление создаст необходимую атмосферу для игрока. В нижней графе стоит то количество секунд, которое показывает, как сразу при входе в локацию начнет играть музыка.

    Хочу немного обратить ваше внимание на цветовую гамму области. Сделав свою раскраску, вы получите непохожую на другие область. Делать это нужно на вкладке “Визуальные”. Кликнув кнопку “Настроить окружающею среду”, откроется окошко, где кликнув по окошку цвета, откроется палитра цветов. Здесь вы можете подобрать нужный вам цвет, правда в игре цветовая гамма будет немного иной…

    В скриптах мы обращаемся к области с помощью функции:
    // Get the area that oTarget is currently in
    // * Return value on error: OBJECT_INVALID

    object GetArea(object oTarget)

    где object oTarget любой объект из этой области. Допустим, мы определяем область, в которой сейчас находиться главный герой. Получим:
    object oPC = GetFirstPC(); // Главный герой
    object oObl = GetArea(oPC); // Наша область

    Правильно задать область нужно для таких функций цикла как:
    object GetFirstObjectInArea(object oArea=OBJECT_INVALID) // первый объект в области
    object GetNextObjectInArea(object oArea=OBJECT_INVALID) // следующий объект области

    Для текущей области мы можем не указывать object oArea, или же просто поставить любой объект, например oPC. Главный объект не является областью, поэтому скрипт будет обрабатывать только текущею область… Например скрипт вывода имени объекта и области, где он находиться:

    void main()
    {
     object oPL = GetFirstObjectInArea(); // первый объект в области
     object oPC = GetFirstPC(); // Главный герой
     while(GetIsObjectValid(oPL))
     {
      SendMessageToPC(oPC, GetName(oPL)+":<c у > "+GetName(GetArea(oPL))+"</c>");
      oPL = GetNextObjectInArea(); // следующий объект
     }
    }

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

    void main()
    {
     // Oбласть объекта с тегом - AREA_1
     object oOb = GetArea(GetObjectByTag("AREA_1")); 
     object oPL = GetFirstObjectInArea(oOb); // первый объект в области
     object oPC = GetFirstPC(); // Главный герой
     while(GetIsObjectValid(oPL))
     {
      SendMessageToPC(oPC, GetName(oPL)+":<c у > "+GetName(GetArea(oPL))+"</c>");
      oPL = GetNextObjectInArea(oOb); // следующий объект
     }
    }

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

    // КОПИЯ ОБЪЕКТА
    void CopyObjectN(object oSource,location locLocation,
         object oOwner = OBJECT_INVALID, string sNewTag = "")
    {CopyObject(oSource, locLocation, oOwner, sNewTag);}
    // Перемещаем создание к точкам днем и ночью
    // object oWPD - Точка день
    // object oWPN - Точка ночь
    // object oPS - Существо перемещаемое
    void pLudey(object oWPD, object oWPN, object oPS);
    
    //***** Перемещение день-ночь *****
    void pLudey(object oWPD, object oWPN, object oPS)
    {
      if (oPS==OBJECT_INVALID||oWPD==OBJECT_INVALID||oWPN==OBJECT_INVALID) return;
      int T = GetTimeHour();
       if(T>=7 && T<21)   // день
       {
        if (GetLocalInt(OBJECT_SELF, "PEREMESH") != 1)
          {
           AssignCommand(oPS, ClearAllActions());
           DelayCommand(0.1, CopyObjectN(oPS, GetLocation(oWPD)));
           DestroyObject(oPS, 0.3);
           DelayCommand(5.0, SetLocalInt(OBJECT_SELF,"PEREMESH",1));
          }
        }
       else if (GetLocalInt(OBJECT_SELF, "PEREMESH") != 2)  // ночь
          {
           AssignCommand(oPS, ClearAllActions());
           DelayCommand(0.1, CopyObjectN(oPS, GetLocation(oWPN)));
           DestroyObject(oPS, 0.3);
           DelayCommand(5.0, SetLocalInt(OBJECT_SELF,"PEREMESH",2));
          }
    }
      // Перемещение существа к точке маршрута
    void main()
    {
      object oPC = GetEnteringObject();
      if (!GetIsPC(oPC)) return;
      object oPointD = GetObjectByTag("WP_TAG_1");
      object oPointM = GetObjectByTag("WP_TAG_2");
      object oPoint1 = GetObjectByTag("WP_TAG_3");
      pLudey(oPoint1, oPointD, GetObjectByTag("TAG_1"));
      pLudey(oPoint1, oPointM, GetObjectByTag("TAG_2"));
    }

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

    //:://////////////////////////////////////////////////
    //:: Чистка локации от предметов, слот ХБ
    //:: Created By: Gennady
    //:: Name: de_chistka_loc
    //:://////////////////////////////////////////////////
    
    ////////////////// Текущий день //////////////////////////
    int Day()
    {
     int nDay = GetCalendarDay();
     int nMes = 28*GetCalendarMonth();
     int nYear = GetCalendarYear(); 
     int iDay = 336*nYear+nMes+nDay;
     return iDay;
    }
    ////////////////////////////////////////////////////////
    void ChistkaLoc(int iD, object oSelf = OBJECT_SELF)
    {
     object oItem = GetFirstObjectInArea(oSelf);
     int iFut = Day()+iD;
     int iDes = Day();
    
     SetLocalInt(oSelf, "day", iFut);
     while(GetIsObjectValid(oItem))
     {
       if(GetLocalInt(oItem,"day")==0)
          SetLocalInt(oItem, "day", iFut);
       if(GetLocalInt(oItem,"day")<= iDes)
       {
         if(GetTag(oItem)=="BodyBag")
         {
          object oItemB = GetFirstItemInInventory(oItem);
          while(GetIsObjectValid(oItemB))
          {
           if(GetPlotFlag(oItemB)==FALSE)
           DestroyObject(oItemB);
           oItemB = GetNextItemInInventory(oItem);
          }
         }
         else
         {
          if(GetObjectType(oItem)==OBJECT_TYPE_ITEM && GetPlotFlag(oItem)==FALSE)
          DestroyObject(oItem);
         }
       }
       oItem = GetNextObjectInArea(oSelf);
     }
    }
    ////////////////////////////////////////////////////////////////////////////////
    void main()
    {
     object oPC = GetFirstPC();
     object oSelf = OBJECT_SELF;
     int iD = StringToInt(GetLockKeyTag(oSelf));
    
    if(GetArea(oPC)==GetArea(oSelf))  // если ПС в локе
      ChistkaLoc(iD);
    else
     if(GetLocalInt(oSelf,"day") <= Day() && GetLocalInt(oSelf,"day")!=0)
      ChistkaLoc(iD);
    }
    // Тэг ключа, врeмя в днях до удаления предметов

    Rambler's Top100