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

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

Монстры

Neverwinter Nights

Монстры


– создания (НПС или NPC), самый сложный объект тулсета. За 2 года знакомства с редактором, я полностью так и не разобрался, что там у них к чему… Но все же поделюсь некоторым накопленным опытом. Для начала ознакомимся с панелью “Письмена”, и за что отвечают скрипты в слотах:

  • OnBloked — операции с дверью, встретившейся на пути.
  • OnCombatRoundEnd — действия в конце раунда боя (раунд 6 секунд)
  • OnConversation — при старте диалога или активации кнопок управления
  • OnDamaged — при уроне
  • OnDeath — при гибели
  • OnDisturbed — при потере или приобретение предметов инвентаря
  • OnHeartBeat — повторяющийся скрипт, цикл каждые 6 секунд
  • OnPerception — при вхождение в зону реакции НПС любого другого существа
  • OnPhysicalAttacked — при физической атаке
  • OnRested — для отдыха
  • OnSpawn — при загрузке модуля, или вызове существа из палитры (один раз)
  • OnSpellCastAt — при колдовстве на НПС
  • OnUserDefined — при поступлении сигнала
  • Панель “Улучшенный”, что на рисунке, служит для настройки фракции существа (отдельно о фракциях см. раздел “Редактор фракций”), настройки лута, т.е. “Модели сокровища”, остающегося после гибели существа. Для того чтобы были эти сокровища, их нужно определить в инвентаре существа. Помните, что все предметы, которые существо получит посредством скриптов, например, при загрузке модуля, т.е. OnSpawn - переходят в разряд выбрасываемых. Также на этой панели можно проставить существу галку “Сюжет”, тогда это создание станет бессмертным, причем ему нельзя будет нанести урон и понизить ХР. У сюжетного создания не работают скрипты слота OnDamaged, и создание при любой атаке не перейдет во враждебное. А вот галка “Бессмертный”, сделает вашего НПС хоть и бессмертным, но у него уже будут работать все скрипты, и фракция при атаке станет враждебной, плюс ХР могут понизиться до одного… Если вам необходимо, то настройте здесь же звук голоса и расстояние чувствительности существа (для скрипта в слоте OnPerception). Обратите внимание, что на этой панели есть строка ResRef существа, который нужен для вызова этого существа из палитры. Более подробно о ResRef и Теге, можно прочитать в разделе “Предметы”.

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

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

    /////////////////////////
    //:: Слот: OnPerception
    /////////////////////////
    void main()
    {
     object oPercep = GetLastPerceived();
     int iS = GetLastPerceptionSeen();
     if(GetIsPC(oPercep)&& iS==TRUE)
     {SetFacingPoint(GetPosition(oPercep)); SpeakString("Привет!");} 
     ExecuteScript("nw_c2_default2", OBJECT_SELF);
    }

    После отклика на реакцию нужно обязательно запустить стандартный скрипт nw_c2_default2 для этого слота. Этот скрипт отвечает за атаку врага в поле зрения НПС. Кроме этого скрипта, реакция на атаку прописана в скриптах для слотов: OnDamaged, OnSpellCastAt, OnPhysicalAttacked.

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

    В базовом же скрипте прописано только выдача некоторого сокровища монстру, и запуск стандартной ходьбы НПС по точкам, смоделированными с помощью редактора. Как сделать такие точки? Очень просто, кликаем по НПС, на НПС должна появиться зеленая окантовка, затем правой кнопкой мыщи, кликаем по нужной точке локации. У вас появится надпись “Создать Точку Маршрута”, кликаем левой кнопкой по надписи, и у вас будет первая точка маршрута. Останется пробить еще хоть одну, и ваш НПС будет уже ходить по этим точкам. Хочу еще отметить, что функция ходьбы WolkWayPoints(), пробита в базовых скриптах для слотов: OnHeartBeat, OnPerception, OnSpawn. Кроме того, во всех диалогах в базовом скрипте nw_wolk_wp на прерывание диалога.

    Если у вас версия редактора позволяет присваивать локальные переменные НПС, то вам может пригодится скрипт спавна By Aiwan WRG Team. Стоит учесть, что этот скрипт довольно громоздкий и если вы замените им базовый, то он будет работать на всех монстрах при загрузке модуля, и если их у вас достаточно много в модуле, то это может затянуть загрузку… К тому же подмечено, что бывают сбои скриптов спавна… Сам скрипт спавна By Aiwan:

    //::///////////////////////////////////////////////
    //:: Name x2_def_spawn
    //:: Copyright © 2001 Bioware Corp.
    //:://////////////////////////////////////////////
    /*
    Default On Spawn script
    
    2003-07-28: Georg Zoeller:
    If you set a ninteger on the creature named
    "X2_USERDEFINED_ONSPAWN_EVENTS"
    The creature will fire a pre and a post-spawn
    event on itself, depending on the value of that
    variable
    1 - Fire Userdefined Event 1510 (pre spawn)
    2 - Fire Userdefined Event 1511 (post spawn)
    3 - Fire both events
    */
    //:://////////////////////////////////////////////
    //:: Created By: Keith Warner, Georg Zoeller
    //:: Created On: June 11/03
    //:://////////////////////////////////////////////
    
    const int EVENT_USER_DEFINED_PRESPAWN = 1510;
    const int EVENT_USER_DEFINED_POSTSPAWN = 1511;
    #include "x0_i0_anims"
    #include "x0_i0_treasure"
    #include "x2_inc_switches"
    void main()
    {
    // User defined OnSpawn event requested?
    int nSpecEvent = GetLocalInt(OBJECT_SELF,"X2_USERDEFINED_ONSPAWN_EVENTS");
    // Pre Spawn Event requested
    if (nSpecEvent == 1 || nSpecEvent == 3 )
    {
    SignalEvent(OBJECT_SELF,EventUserDefined(EVENT_USER_DEFINED_PRESPAWN ));
    }
    /* Fix for the new golems to reduce their number of attacks */
    int nNumber = GetLocalInt(OBJECT_SELF,CREATURE_VAR_NUMBER_OF_ATTACKS);
    if (nNumber >0 )
    {
    SetBaseAttackBonus(nNumber);
    }
    //::====================== Add By Aiwan / WRG! Team / ==========================
    //:://////////////////////////////////////////////
    //:: OnSpawn am_csp_common
    //:: Copyright © 2005 WRG!
    //:://////////////////////////////////////////////
    /*
    Добавляем стандартные свойства НПС, модифицируя
    скрипт NW_C2_DEFAULT9.
    
    КОММЕНТАРИИ
    
    Каждому свойству принадлежит локальная переменная,
    которую необходимо установить на НПС в редакторе.
    Все они начинаются на "FLAG_+Спавн_Свойство_НПС"
    Далее...
    
    * Если на модуль повесить LocalInt
    "X2_SWITCH_CROSSAREA_WALKWAYPOINTS" равную TRUE;
    то все НПС ходят по вейпам из локации в локацию.
    В противном случае только по вейпоинтам в одной
    локации.
    
    * Если повесить на дневной пост POST_ или ночной
    пост NIGHT_ локальную переменную LocalInt
    "X2_L_WAYPOINT_SETFACING" равную TRUE, то НПС
    становится лицом в сторну вепоинта. И возвращется
    в ту сторону даже после диалога/боя.
    
    */
    //:://////////////////////////////////////////////
    //:: Created By: Aiwan
    //:: Original idea By: DBColl
    //:: Created On: 06.07.2005
    //:://////////////////////////////////////////////
    //==============================================================================
    // Стандартные еванты скрипта NW_C2_DEFAULT9
    //==============================================================================
    if (GetCreatureFlag(OBJECT_SELF, CREATURE_VAR_USE_SPAWN_STEALTH) == TRUE)
    {SetSpawnInCondition(NW_FLAG_STEALTH);}
    if (GetCreatureFlag(OBJECT_SELF, CREATURE_VAR_USE_SPAWN_SEARCH) == TRUE)
    {SetSpawnInCondition(NW_FLAG_SEARCH);}
    if (GetCreatureFlag(OBJECT_SELF, CREATURE_VAR_USE_SPAWN_AMBIENT_IMMOBILE) == TRUE)
    {SetSpawnInCondition(NW_FLAG_IMMOBILE_AMBIENT_ANIMATIONS);}
    if (GetCreatureFlag(OBJECT_SELF, CREATURE_VAR_USE_SPAWN_AMBIENT) == TRUE)
    {SetSpawnInCondition(NW_FLAG_AMBIENT_ANIMATIONS);}
    //--------------------------------------------------------------------------
    // НПС ходит скрытно, когда выполняется WalkWaypoints(). Хорошо для убийц.
    //--------------------------------------------------------------------------
    if (GetLocalInt(OBJECT_SELF, "FLAG_STEALTH") == TRUE)
    {SetSpawnInCondition(NW_FLAG_STEALTH);}
    //--------------------------------------------------------------------------
    // То же самое, только в режиме поиска. Хорошо для стражи.
    //--------------------------------------------------------------------------
    if (GetLocalInt(OBJECT_SELF, "FLAG_SEARCH") == TRUE)
    {SetSpawnInCondition(NW_FLAG_SEARCH);}
    //--------------------------------------------------------------------------
    // НПС появится используя "EffectAppear", т.е. Свалится с неба.
    //--------------------------------------------------------------------------
    if (GetLocalInt(OBJECT_SELF, "FLAG_APPEAR_ANIMATION") == TRUE)
    {SetSpawnInCondition(NW_FLAG_APPEAR_SPAWN_IN_ANIMATION);}
    //--------------------------------------------------------------------------
    // НПС будет постоянно воспроизводить анимацию, даже если игрок уйдет из его
    // поля зрения.
    //--------------------------------------------------------------------------
    if (GetLocalInt(OBJECT_SELF, "FLAG_CONSTANT") == TRUE)
    {SetSpawnInCondition(NW_ANIM_FLAG_CONSTANT);}
    //--------------------------------------------------------------------------
    // Цивилизованные творения взаимодействуют с placeables в их области, что
    // имеют таг "NW_INTERACTIVE"
    //--------------------------------------------------------------------------
    if (GetLocalInt(OBJECT_SELF, "FLAG_CIVILIZED") == TRUE)
    {SetAnimationCondition(NW_ANIM_FLAG_IS_CIVILIZED);}
    //--------------------------------------------------------------------------
    // НПС с бродячей анимацией ходят бродят по городу часто возвращаясь в свой
    // SPAWN пункт, арену в которой они спавнятся (желательно внутреннюю) и
    // которая содержит один из тагов: "NW_HOME", "NW_TAVERN", "NW_SHOP"
    //--------------------------------------------------------------------------
    if (GetLocalInt(OBJECT_SELF, "FLAG_CLOSE_RANGE") == TRUE)
    {SetAnimationCondition(NW_ANIM_FLAG_IS_MOBILE_CLOSE_RANGE);}
    //--------------------------------------------------------------------------
    // При наступлении ночи, запускает скрипт сна на НПС. Имя скрипта вешается
    // на модуль, как LocalString "X2_S_SLEEP_AT_NIGHT_SCRIPT" равное имени
    // скрипта "имя_скрипта_на_ночь".
    //--------------------------------------------------------------------------
    if (GetLocalInt(OBJECT_SELF, "FLAG_SLEEPING_AT_NIGHTT") == TRUE)
    {SetSpawnInCondition(NW_FLAG_SLEEPING_AT_NIGHT);}
    //--------------------------------------------------------------------------
    // НПС поднимает тревогу, сообщит при атаке всем не враждебным НПС.
    //--------------------------------------------------------------------------
    if (GetLocalInt(OBJECT_SELF, "FLAG_WARNINGS") == TRUE)
    {SetSpawnInCondition(NW_FLAG_SET_WARNINGS);}
    //--------------------------------------------------------------------------
    // Ходит на ночные/дневные поинты. Днем ходит по стандартным поинтам, ночью
    // "WN_" + NPC Tag + "_##. Ночной пост - "NIGHT_" + NPC tag.
    //--------------------------------------------------------------------------
    if (GetLocalInt(OBJECT_SELF, "FLAG_DAY_NIGHT_POSTING") == TRUE)
    {SetSpawnInCondition(NW_FLAG_DAY_NIGHT_POSTING);}
    //--------------------------------------------------------------------------
    // НПС болтает с дружественными НПС, воспроизводит анимацию, эмитирует
    // оживление и активность.
    //--------------------------------------------------------------------------
    if (GetLocalInt(OBJECT_SELF, "FLAG_IMMOBILE_ANIMATIONS") == TRUE)
    {SetSpawnInCondition(NW_FLAG_IMMOBILE_AMBIENT_ANIMATIONS);}
    //--------------------------------------------------------------------------
    // Почти то же что и выше, за исключением того, что НПС бродит по округе.
    //--------------------------------------------------------------------------
    if (GetLocalInt(OBJECT_SELF, "FLAG_AMBIENT_ANIMATIONS") == TRUE)
    {SetSpawnInCondition(NW_FLAG_AMBIENT_ANIMATIONS);}
    
    //==============================================================================
    // Атакующие свойства НПС - устанавливается ТОЛЬКО ОДНО ИЗ НИХ!
    //==============================================================================
    //--------------------------------------------------------------------------
    // Старается атаковать издали, дальнобойным оружием.
    //--------------------------------------------------------------------------
    if (GetLocalInt(OBJECT_SELF, "FLAG_RANGED") == TRUE)
    {SetCombatCondition(X0_COMBAT_FLAG_RANGED);}
    //--------------------------------------------------------------------------
    // Использует защитные фиты и паррирование
    //--------------------------------------------------------------------------
    if (GetLocalInt(OBJECT_SELF, "FLAG_DEFENSIVE") == TRUE)
    {SetCombatCondition(X0_COMBAT_FLAG_DEFENSIVE);}
    //--------------------------------------------------------------------------
    // Будет невидимым и попытается атаковать, выйдет из невидимости и снова
    // будет пытаться атаковать.
    //--------------------------------------------------------------------------
    if (GetLocalInt(OBJECT_SELF, "FLAG_AMBUSHER") == TRUE)
    {SetCombatCondition(X0_COMBAT_FLAG_AMBUSHER);}
    //--------------------------------------------------------------------------
    // При атаке убегает
    //--------------------------------------------------------------------------
    if (GetLocalInt(OBJECT_SELF, "FLAG_COWARDLY") == TRUE)
    {SetCombatCondition(X0_COMBAT_FLAG_COWARDLY);}
    //==============================================================================
    // Защитныеые свойства НПС - устанавливается ТОЛЬКО ОДНО ИЗ НИХ!
    //==============================================================================
    //--------------------------------------------------------------------------
    // НПС Убегает в точку и возвращается через короткое время позже.
    //--------------------------------------------------------------------------
    if (GetLocalInt(OBJECT_SELF, "FLAG_ESCAPE_RETURN") == TRUE)
    {SetSpawnInCondition(NW_FLAG_ESCAPE_RETURN);}
    //--------------------------------------------------------------------------
    // Убегает в точку и не возвращается.
    //--------------------------------------------------------------------------
    if (GetLocalInt(OBJECT_SELF, "FLAG_ESCAPE_LEAVE") == TRUE)
    {SetSpawnInCondition(NW_FLAG_ESCAPE_LEAVE);}
    //--------------------------------------------------------------------------
    // Телепортируется в безопасность и не возвращается.
    //--------------------------------------------------------------------------
    if (GetLocalInt(OBJECT_SELF, "FLAG_TELEPORT_LEAVE") == TRUE)
    {SetSpawnInCondition(NW_FLAG_TELEPORT_LEAVE);}
    //--------------------------------------------------------------------------
    // Телепортируется в безопасность и возвращается через короткое время.
    //--------------------------------------------------------------------------
    if (GetLocalInt(OBJECT_SELF, "FLAG_TELEPORT_RETURN") == TRUE)
    {SetSpawnInCondition(NW_FLAG_TELEPORT_RETURN);}
    //==============================================================================
    // Добавляем свои настройки для персональных нужд.
    //==============================================================================
    //--------------------------------------------------------------------------
    // Присваиваем НПС координаты, что бы он стоял лицом в одном направлении.
    //--------------------------------------------------------------------------
    if (GetLocalFloat(OBJECT_SELF,"initFacing") == 0.0)
    {
    location loc = GetLocation(OBJECT_SELF);
    SetLocalFloat(OBJECT_SELF, "initFacing", GetFacingFromLocation(loc));
    SetLocalLocation(OBJECT_SELF, "initLoc", loc);
    }
    //==============================================================================
    // Запускаем стандартные еванты на НПС
    //==============================================================================
    //--------------------------------------------------------------------------
    // Если вы присвоите локальную переменную "FLAG_USER_DEFINED_EVENT" равную
    // TRUE, то все еванты присвоятся сразу. В противном случае присваивайте
    // По одному для каждого события.
    //--------------------------------------------------------------------------
    int iUserDef = GetLocalInt(OBJECT_SELF, "FLAG_USER_DEFINED_EVENT");
    
    // * Fire User Defined Event 1001 in the OnHeartbeat
    if (iUserDef==TRUE || GetLocalInt(OBJECT_SELF, "FLAG_HEARTBEAT") == TRUE)
    {SetSpawnInCondition(NW_FLAG_HEARTBEAT_EVENT);}
    
    // * Fire User Defined Event 1002
    if (iUserDef==TRUE || GetLocalInt(OBJECT_SELF, "FLAG_PERCIEVE") == TRUE)
    {SetSpawnInCondition(NW_FLAG_PERCIEVE_EVENT);}
    
    // * Fire User Defined Event 1003
    if (iUserDef==TRUE || GetLocalInt(OBJECT_SELF, "FLAG_END_COMBAT_ROUND") == TRUE)
    {SetSpawnInCondition(NW_FLAG_END_COMBAT_ROUND_EVENT);}
    
    // * Fire User Defined Event 1004
    if (iUserDef==TRUE || GetLocalInt(OBJECT_SELF, "FLAG_ON_DIALOGUE") == TRUE)
    {SetSpawnInCondition(NW_FLAG_ON_DIALOGUE_EVENT);}
    
    // * Fire User Defined Event 1005
    if (iUserDef==TRUE || GetLocalInt(OBJECT_SELF, "FLAG_ATTACK") == TRUE)
    {SetSpawnInCondition(NW_FLAG_ATTACK_EVENT);}
    
    // * Fire User Defined Event 1006
    if (iUserDef==TRUE || GetLocalInt(OBJECT_SELF, "FLAG_DAMAGED") == TRUE)
    {SetSpawnInCondition(NW_FLAG_DAMAGED_EVENT);}
    
    // * Fire User Defined Event 1007 (Считается ненадежным BioWare)
    if (iUserDef==TRUE || GetLocalInt(OBJECT_SELF, "FLAG_DEATH") == TRUE)
    {SetSpawnInCondition(NW_FLAG_DEATH_EVENT);}
    
    // * Fire User Defined Event 1008
    if (iUserDef==TRUE || GetLocalInt(OBJECT_SELF, "FLAG_DISTURBED") == TRUE)
    {SetSpawnInCondition(NW_FLAG_DISTURBED_EVENT);}
    
    // * Fire User Defined Event
    if (iUserDef==TRUE || GetLocalInt(OBJECT_SELF, "FLAG_SPELL_CAST_AT") == TRUE)
    {SetSpawnInCondition(NW_FLAG_SPELL_CAST_AT_EVENT);}
    //--------------------------------------------------------------------------
    // Создает небольшую сумму сокровища в инвентаре
    //--------------------------------------------------------------------------
    if ((GetLocalInt(GetModule(), "X2_L_NOTREASURE") == FALSE) &&
    (GetLocalInt(OBJECT_SELF, "X2_L_NOTREASURE") == FALSE) )
    {
    CTG_GenerateNPCTreasure(TREASURE_TYPE_MONSTER, OBJECT_SELF);
    }
    //--------------------------------------------------------------------------
    SetListeningPatterns();
    //--------------------------------------------------------------------------
    // nRun - если нужно что бы стражник бегал или ждал на поинтах больше чем по
    // умолчанию, то присвойте нужные значения на OBJECT_SELF.
    // LocalInt "FLAG_WALK_RUN" - равная TRUE, заставит НПС бегать по точкам.
    // LocalFloat "FLAG_WALK_PAUSE" - время паузы на поинтах. По умолчанию f1.0
    // Для верной работы надо править еще эту функцию на HertBeat скрипте и в
    // после завершения диалога с НПС. Иначе все настройки будут по умолчанию
    // после первого боя или диалога.
    //--------------------------------------------------------------------------
    int nRun = GetLocalInt(OBJECT_SELF, "FLAG_WALK_RUN");
    float fPause = GetLocalFloat(OBJECT_SELF, "FLAG_WALK_PAUSE");
    if (fPause == 0.0f)
    {
    fPause = 1.0f;
    }
    WalkWayPoints(nRun, fPause);
    //=========================== End Aiwan's Edition ==============================
    //Post Spawn event requeste
    if (nSpecEvent == 2 || nSpecEvent == 3)
    {
    SignalEvent(OBJECT_SELF,EventUserDefined(EVENT_USER_DEFINED_POSTSPAWN));
    }
    }

    Henchman

    Эти все скриты годятся только для НПС, а вот для henchman или спутника героя, они не годятся… Для этого служат специальные скрипты, в старой версии они начинаются на nw_ch_ac + цифра или буква из скриптов НПС для этого слота. Для аддона СоУ были написаны новые скрипты для спутников, правда они не очень-то отличаются от базовых, просто туда добавлены некоторые различия для разных спутников… Самое заметное отличие разве что в скрипте nw_ch_ac4 для слота OnConversation, где добавлена возможность просмотра инвентаря спутника. Если подправить строку:
    bkRespondToHenchmenShout(oShouter, nMatch, oIntruder, TRUE);
    заменив на такую
    bkRespondToHenchmenShout(oShouter, nMatch, oIntruder);
    то тогда появится инвентарь спутника и у старого скрипта… Большинство функций управления спутников, прописаны в инклюде #include "x0_inc_henai".

    Таблица скриптов для спутников (henchman)

    OnBloked

    nw_ch_ace

    x0_ch_hen_block

    OnCombatRoundEnd

    nw_ch_ac3

    x0_ch_hen_combat

    OnConversation

    nw_ch_ac4

    x0_ch_hen_conv

    OnDamaged

    nw_ch_ac6

    x0_ch_hen_damage

    OnDeath

    nw_ch_ac7

    x0_ch_hen_death

    OnDisturbed

    nw_ch_ac8

    x0_ch_hen_distrb

    OnHeartBeat

    nw_ch_ac1

    x0_ch_hen_heart

    OnPerception

    nw_ch_ac2

    x0_ch_hen_percep

    OnPhysicalAttacked

    nw_ch_ac5

    x0_ch_hen_attack

    OnRested

    nw_ch_aca

    x0_ch_hen_rest

    OnSpawn

    nw_ch_ac9

    x0_ch_hen_spawn

    OnSpellCastAt

    nw_ch_acb

    x0_ch_hen_spell

    OnUserDefined

    nw_ch_acd

    x0_ch_hen_usrdef

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

    //:://////////////////////
    //:: Слот: OnSpawn
    //:://////////////////////
    #include "X0_INC_HENAI"
    void main()
    {
        //User ranged weapons by default if true.
        SetAssociateState(NW_ASC_USE_RANGED_WEAPON, FALSE);
        SetAssociateState(NW_ASC_DISTANCE_2_METERS);
        // Труп не исчезает и будет юзабельным
        SetIsDestroyable(FALSE, TRUE, TRUE); 
        int nHenchLevel = GetHitDice(OBJECT_SELF);
    
        if (nHenchLevel == 1)
        {
          if (GetTag(OBJECT_SELF) == "TAG")
            {
                // Класс воин
             LevelUpHenchman(OBJECT_SELF, CLASS_TYPE_FIGHTER);
                // Класс СВЯЩЕННИК
             LevelUpHenchman(OBJECT_SELF, CLASS_TYPE_CLERIC);
            }
        }
    }

    Остальные базовые скрипты нет большой необходимости править, особенно если вы в них не очень разбираетесь. Рассмотрим лучше как присоединить спутника к герою. В диалог Спутника ставим скрипт на ветку присоединения, в слот действия. Скрипт сразу отсоединит старого спутника, если он есть, и заменит его новым:

    void main()
    {
        object oPC = GetPCSpeaker();
        object oSP = GetHenchman(oPC);
    
      if(OBJECT_SELF == oSP) return;
      if(oSP!=OBJECT_INVALID)
        {
         AssignCommand(oSP, ClearAllActions());
         RemoveHenchman(GetPCSpeaker(),oSP); // Удалить спутника
         AddHenchman(GetPCSpeaker(), OBJECT_SELF); // Дать нового спутника
        }
        else
        {
         AddHenchman(GetPCSpeaker(), OBJECT_SELF); // Дать нового спутника
         return;
        }
        AssignCommand(oSP, SpeakString("Я подожду вас здесь. До скорой встречи!"));
    }

    Если нужно просто удалить спутника:

    void main()
    {
        object oPC = GetPCSpeaker();
        object oSP = GetHenchman(oPC);
        if (GetIsObjectValid(oSP))
        {
         AssignCommand(oSP, ClearAllActions());
         AssignCommand(oSP, SpeakString("Я подожду вас здесь. До скорой встречи!"));
         RemoveHenchman(GetPCSpeaker(),oSP); // Удалить спутника
        }
    }

    Вот еще простенький скрипт на проверку веток диалога спутника, если он уже в команде героя:

    int StartingConditional()
    {
      object oPC = GetPCSpeaker();
      object oSP = GetHenchman(oPC);
      if (!(GetIsObjectValid(oSP) && oSP == OBJECT_SELF))
      return FALSE;
      return TRUE;
    }

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

    void main()
      {
        object oPC = GetPCSpeaker();
        object oSP = GetHenchman(oPC);
        int LoPC = GetHitDice(oPC);
        int LoSP = GetHitDice(oSP);
    
      while (LoPC > LoSP)
      {
       LevelUpHenchman(OBJECT_SELF, CLASS_TYPE_FIGHTER);
       LoSP++;
      }
    }
    /* Повысить уровень воина до уровня героя */

    Чтобы придать игре некоторую интерактивность, рассмотрим пару триггеров голоса, верней текста над головой Спутника. Первый скрипт подобен скрипту для героя из раздела “Триггеры”, но все же запишем и для Спутника:

    //:://////////////////////////////////////////////
    //:: ON ENTER ТРИГГЕРА.
    //:://////////////////////////////////////////////
    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 (GetTag(oPC) != "KARL") return;
     if (sPer == "")
         DestroyObject(OBJECT_SELF, 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, то повтор текста (цифра > 1)
        Если запись не цифра, то присвоить переменную герою с тегом ключа
        Если тег цифра, то это количество всплываний текста
        Скрипты управления переменных: prov_trigger_g...prov_trigger_del
        Для триггера с тегом   TRIGGER_G    */

    Бывает необходимость отреагировать спутником на определенное место области, при этом, чтобы в других областях этого события не было отражено в диалоге спутника. Тогда на вход триггера ставим верхний скрипт, и задаем ему Тег "TRIGGER_G ", и записываем еще два скрипта управления.

    //:://////////////////////////////////////////////
    //:: Проверить есть ли рЯдом триггер с тегом "TRIGGER_G"
    //:: File name: prov_trigger_g1
    //:://////////////////////////////////////////////
    int StartingConditional()
    {
     object oTrig = GetNearestObjectByTag("TRIGGER_G");
     string sPer = GetLockKeyTag(oTrig);
    
     if ((GetIsObjectValid(oTrig) && sPer == "LOKA_1")  
       && GetLocalInt(GetFirstPC(), sPer) == 1)
      return TRUE;
      return FALSE;
    }

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

    //:://////////////////////////////////////////////
    //:: Проверить есть ли рЯдом триггер с тегом "TRIGGER_G"
    //:: File name: prov_trigger_del
    //:://////////////////////////////////////////////
    int StartingConditional()
    {
     object oTrig = GetNearestObjectByTag("TRIGGER_G");
     string sPer = GetLockKeyTag(oTrig);
    
     if (GetIsObjectValid(oTrig) && GetLocalInt(GetFirstPC(), sPer) == 1)
     {
      DestroyObject(oTrig, 0.3);
      return TRUE;
     }
      return FALSE;
    }

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

    Summon

    Для полного комплекта команды героя, попробуем создать призываемое существо – Summon. Для этого, создайте в палитре нужного монстра, и поставьте ему скрипты такие же как и у спутника. Теперь можно призвать это существо, допустим, создав некий Камень Призыва, и проставив на нем использование один раз в день. Запишем код в скрипт модульных свойств:

    //:://///////////////////////
    //:: СЛОТ: OnActivateItem
    //:://///////////////////////
    void main()
    {
     object oActivator = GetItemActivator(); // это активатор объекта(наш PC)
     object oActivated = GetItemActivated(); // это активируемый объект
     string sTag = GetTag(oActivated);
    
     //  *****   КАМЕНЬ ПРИЗЫВА *****
     if (sTag  == "KAMEN_X")
     {
      effect Summon = EffectSummonCreature("ResRef", VFX_FNF_SUMMON_MONSTER_3); 
      ApplyEffectToObject(DURATION_TYPE_PERMANENT,
      SupernaturalEffect(Summon), oActivator);
      return;
     }
    }

    Скрипты

    Теперь несколько готовых скриптов, начнем традиционно со скрипта смерти НПС для слота OnDeath. Многие хотят видеть труп монстра, а не простой мешочек после гибели существа. Как такое сделать в своем модуле? Для этого вам нужно создать невидимый контейнер в палитре, который будет оставаться на месте гибели НПС, и поставить на него скрипт удаления погибшего существа, т.к. после смерти движок игры не воспринимает сприптов прописанных на этом НПС. Настройки у объекта такие: сюжет, используемый, есть инвентарь, ResRef = "invisobj001".
    Скрипт для контейнера:

    //:://////////////////////////////////////////////////
    //:: Уничтожить ближайший труп с тэгом контейнера и весь его инвентарь
    //:: Слот: OnClose 
    //:: File name: monster_destroy
    //:://////////////////////////////////////////////////
    void main()
    {
      object oSelf = OBJECT_SELF;
      string sNPC = GetTag(oSelf);
      object oNPC = GetNearestObjectByTag(sNPC);
      int i;
      object oItem = GetItemInSlot(i, oNPC);
    
      while (i<=13)
       {
        SetDroppableFlag(oItem, FALSE);
        i++;
        oItem = GetItemInSlot(i, oNPC);
       }
     AssignCommand(oNPC, SetIsDestroyable(TRUE, TRUE, TRUE));
     DestroyObject(oNPC, 0.5);
     DestroyObject(oSelf, 0.7);
    }

    Скрипт, который должен стоять в слоте монстра:

    //:://////////////////////////////////////////////////
    //:: Слот: OnDeath
    //:: File name: monster_death
    //:: Скрипт на OnClose контейнере monster_destroy
    //:://////////////////////////////////////////////////
    void main()
    {
    object oNPC = OBJECT_SELF;
    string sResRef = "invisobj001";// труп в палитре
    string sTag = GetTag(oNPC);//ТEГ убитого НПС
    location lLoc = GetLocation(oNPC);
    object oTrup = CreateObject(OBJECT_TYPE_PLACEABLE, sResRef, lLoc, TRUE, sTag);
    object oItem = GetFirstItemInInventory(oNPC);
    effect eKosti = EffectVisualEffect(VFX_COM_CHUNK_RED_LARGE);// Кровь и кости
    effect eNez = EffectVisualEffect(VFX_FNF_SUMMON_UNDEAD);// Взрыв и голос
    int i;
    int nGold = GetGold(oNPC); // золото у NPC
    int iRac = GetRacialType(oNPC);
    
     SetIsDestroyable(FALSE);// Труп не исчезает
     if(iRac == RACIAL_TYPE_UNDEAD || iRac == RACIAL_TYPE_ELEMENTAL ||
        iRac == RACIAL_TYPE_CONSTRUCT)
     ApplyEffectAtLocation(DURATION_TYPE_INSTANT,eNez,lLoc);
     else
     ApplyEffectAtLocation(DURATION_TYPE_INSTANT,eKosti,lLoc);
     CreateItemOnObject("nw_it_gold001", oTrup, nGold); // дать золото
     AssignCommand(oNPC, TakeGoldFromCreature(nGold, oNPC, TRUE));// забрать золото
    while (GetIsObjectValid(oItem))// правильный
        {
         if (GetDroppableFlag(oItem) == TRUE)
           CopyObject(oItem, lLoc, oTrup);
           DestroyObject(oItem);
         oItem = GetNextItemInInventory(oNPC);
        }
    oItem = GetItemInSlot(i,oNPC);
    
    while (i<=13)
       {
        if (GetDroppableFlag(oItem) == TRUE)
          CopyItem(oItem, oTrup);
          i++;
        oItem = GetItemInSlot(i,oNPC);
       }
     PlayVoiceChat(VOICE_CHAT_DEATH);
    }

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

    //:://////////////////////////////////////////////
    //:: ДЛЯ НЕВИДИМОГО ОБЪЕКТА
    //:: Воскресить существо
    //:: Слот: OnHeartBeat 
    //:://////////////////////////////////////////////
    void main()
    {
     if(GetLocalInt(OBJECT_SELF,"ODIN") != 1)
    {
      DestroyObject(OBJECT_SELF,125.0);
      string sTag = GetTag(OBJECT_SELF);
      object oNPC = GetNearestObjectByTag(sTag);
      ActionWait(120.0);
      ActionDoCommand(AssignCommand(oNPC, SetIsDestroyable(TRUE)));
      ActionCastSpellAtObject(SPELL_RESURRECTION, oNPC, METAMAGIC_QUICKEN, TRUE);
      SetLocalInt(OBJECT_SELF,"ODIN",1);
     }
    }

    Скрипт для монстра:

    //:://////////////////////////////////////////////
    //:: ДЛЯ ВОСКРЕШАЕМОГО МОНСТРА
    //:: Слот: OnDeath 
    //:://////////////////////////////////////////////
    void main()
    {
    object oNPC = OBJECT_SELF;
    if(GetLocalInt(OBJECT_SELF,"SMERT") != 1)
    {
     string sResRef = "trup_01"; // труп в палитре
     string sTag = GetTag(oNPC); 
     location Loc = GetLocation(oNPC);
     object oTrup = CreateObject(OBJECT_TYPE_PLACEABLE, sResRef, Loc, TRUE, sTag);
     SetIsDestroyable(FALSE, TRUE, FALSE); // чтоб тело убитого НПС не исчезало
     SetLocalInt(OBJECT_SELF,"SMERT",1);
    }
    else
    ExecuteScript("monster_death", oNPC);
    }

    Бывает необходимость провести что-то типа дуэли с НПС, как в первом Невере. Тогда вам поможет скрипт прекращения атаки всех существ в радиусе 30 метров от героя. Учтите, скрипт не меняет фракционных отношений! Если нужно вернуть нормальное отношение фракции, то пропишите это в диалоге, на проверку локалки KONEZ_BOY на НПС, или в скрипте проверки сигнала на невидимом ближайшем объекте с тегом S_BOYA. НПС - носитель скрипта, должен быть бессмертным, чтобы случайным критическим ударом не отправить его на тот свет…

    //:://////////////////////////////////////////////////
    //:: Прекращение атаки
    //:: File name: end_attak
    //:: Слот: OnDamaged, OnCombatRoundEnd, OnHeartBeat 
    //:://////////////////////////////////////////////////
    void main()
    {
     object oPC = GetFirstPC();
     object oSelf = OBJECT_SELF;
     object oNPC = GetFirstObjectInShape(SHAPE_SPHERE, 30.0,GetLocation(oPC),TRUE);
    
      if (GetCurrentHitPoints(oSelf) < 30 && GetLocalInt(oSelf, "KONEZ_BOY") != 1)
      {
       while (GetIsObjectValid(oNPC))  // Все существа в радиусе 30 метров
       {
        if(GetIsInCombat(oNPC) || GetIsEnemy(oPC, oNPC))
        {
         AssignCommand(oNPC, SurrenderToEnemies());
         AssignCommand(oNPC, ClearAllActions(TRUE));
        }
        oNPC = GetNextObjectInShape(SHAPE_SPHERE, 30.0, GetLocation(oPC), TRUE);
       }
       SetLocalInt(oSelf,"KONEZ_BOY",1);
       AssignCommand(oPC, ClearAllActions(TRUE));
       SpeakString("Я сдаюсь... Прекратим бой!");
       AssignCommand(oSelf, ActionPlayAnimation(ANIMATION_FIREFORGET_BOW));
       DelayCommand(1.5, PlaySound("as_pl_wailingm2"));
       int nHeal = GetMaxHitPoints(oSelf) - GetCurrentHitPoints(oSelf);
       effect eHeal = EffectHeal(nHeal);
       DelayCommand(100.0, ApplyEffectToObject(DURATION_TYPE_INSTANT, eHeal, oSelf));
       DelayCommand(100.0, SetLocalInt(oSelf,"KONEZ_BOY",FALSE));
       // Дать сигнал
       SignalEvent(GetNearestObjectByTag("S_BOYA"),EventUserDefined(777)); 
      }
    }
    /*   Все существа в радиусе 30 метров прекращают бой.
      Владелиц скрипта, получает переменную для начала диалога.
      Через 100 сек. снимается эта переменная и лечится персонаж.
      Подается сигнал на ближайший объект с тегом  S_BOYA    */

    Вот еще два скрипта для сидящих НПС. Один для ближайшего стула с тегом ST_ + тег НПС, другой просто на полу… Для правильного положения сидящего на полу НПС, нужна точка с тегом V_ + тег НПС. Скрипты ставятся в слот OnHeartBeat:

    //:://////////////////////////////////////////////////
    //:: ДЛЯ СИДЯЩИХ НА СТУЛЕ НПС
    //:: File name: de_sit_chair
    //:: Cлот: OnHeartBeat
    //:://////////////////////////////////////////////////
    void main()
    {
     if(GetArea(GetFirstPC()) != GetArea(OBJECT_SELF)) return; // если ПС нет в локе
       object oCHAIR = GetNearestObjectByTag("ST_"+ GetTag(OBJECT_SELF));
       object oSelf = OBJECT_SELF;
       int iAnim;
     if (GetIsInCombat(oSelf))
     {ExecuteScript("nw_c2_default1", oSelf); return;}
     if (!IsInConversation(oSelf))
     {
       object oSit = GetSittingCreature(oCHAIR);
       if(oSelf == oSit) return; //Если уже сидит
      if (GetIsObjectValid(oCHAIR) == TRUE && GetDistanceBetween(oSelf, oCHAIR) < 15.0)
          AssignCommand(oSelf, ActionSit(oCHAIR));
      else
        {
          AssignCommand(oSelf, ClearAllActions(TRUE));
          switch (Random(3)+1)
          {
           case 1: iAnim = ANIMATION_FIREFORGET_PAUSE_SCRATCH_HEAD; break;
           case 2: iAnim = ANIMATION_FIREFORGET_HEAD_TURN_LEFT; break;
           case 3: iAnim = ANIMATION_FIREFORGET_HEAD_TURN_RIGHT; break;
          }
          AssignCommand(oSelf, ActionPlayAnimation(iAnim));
        }
     }
    }

    Скрипт для сидящего на полу:

    //:://////////////////////////////////////////////////
    //:: ДЛЯ СИДЯЩИХ НА ПОЛУ НПС
    //:: File name: de_sit_cross
    //:: Cлот: OnHeartBeat
    //:://////////////////////////////////////////////////
    void main()
    {
      object oWay = GetWaypointByTag("V_" + GetTag(OBJECT_SELF));
      vector vFace = GetPosition(oWay);
    
     if(GetArea(GetFirstPC()) != GetArea(OBJECT_SELF)) return; // если ПС нет в локе
     if (GetIsInCombat(OBJECT_SELF))
     {ExecuteScript("nw_c2_default1", OBJECT_SELF); return;}
     if (!IsInConversation(OBJECT_SELF))
      {
       SetFacingPoint(vFace);
       switch (Random(3)+1)
       {
        case 1:
        if(Random(100) >= 50)
        ActionPlayAnimation(ANIMATION_LOOPING_SIT_CROSS);
        else
        ActionPlayAnimation(ANIMATION_FIREFORGET_DRINK);
        break;
        case 2:
        if(Random(100) >= 50)
        ActionPlayAnimation(ANIMATION_FIREFORGET_PAUSE_SCRATCH_HEAD);
        else
        ActionPlayAnimation(ANIMATION_FIREFORGET_DRINK);
        break;
        case 3:
        if(Random(100)>=50 && GetAbilityScore(OBJECT_SELF,ABILITY_INTELLIGENCE)>=17)
        ActionPlayAnimation(ANIMATION_FIREFORGET_READ);
        else
        {
         if (GetGender(OBJECT_SELF)==GENDER_MALE)
         PlaySound("as_pl_yawningm1");
         else
         PlaySound("as_pl_yawningf1");
         ActionPlayAnimation(ANIMATION_FIREFORGET_PAUSE_SCRATCH_HEAD);
        }
        break;
       }
       ActionPlayAnimation(ANIMATION_LOOPING_SIT_CROSS, 1.0, 6.0);
      }
    }
    /* Персонаж сидит на земле, пьет и чешет голову.
    Если интеллект выше 17, то еще и читает.    */

    Чтобы НПС не сидел сиднем в таверне, а делал какую-либо анимацию, нужно поставить заниженный объект на 0.25 метра, и поставить точку, где будет предполагаемое место для сидения НПС. Скрипт для такого НПС:

    //:://///////////////////////////////////
    //:: ДЛЯ СИДЯЩИХ NРС С АНИМАЦИЕЙ
    //:: Слот: OnHeartBeat
    //:: Created By: Gennady
    //:: File name: de_sit_no_st
    //:://///////////////////////////////////
    void main()
    {
     object oNPC = OBJECT_SELF;
     if(GetArea(GetFirstPC()) != GetArea(oNPC)) return; // если ПС нет в локе
     if (GetIsInCombat(oNPC))return;  // Если атакован
     if (IsInConversation(oNPC))return; // Если говорит
     if (GetLocalInt(oNPC, "SIT") == 1)
     {ExecuteScript("nw_c2_default1", oNPC); return;}
    
      object oWay = GetNearestObjectByTag("PV_" + GetTag(oNPC));
      if (oWay == OBJECT_INVALID)
      {ExecuteScript("nw_c2_default1", oNPC); return;}
      object oPC = GetFirstPC();
      float fFacing = GetFacing(oWay);
    
     ClearAllActions();
     if (GetDistanceBetween(oWay, oNPC) >= 0.2)
      ActionMoveToObject(oWay, FALSE, 0.0);
     ActionDoCommand(SetFacing(fFacing));
     ActionPlayAnimation(ANIMATION_LOOPING_SIT_CHAIR, 1.0, 3.0);
     if(GetAbilityScore(oNPC, ABILITY_INTELLIGENCE) >= 17)
     ActionPlayAnimation(ANIMATION_FIREFORGET_READ);
     else
     ActionPlayAnimation(ANIMATION_FIREFORGET_DRINK);
     if (GetDistanceBetween(oPC, oNPC) < 3.0 && GetObjectSeen(oPC, oNPC))
     {
      ActionSpeakString("Привет!");
    
        if (GetGender(oNPC)==GENDER_MALE)
            PlaySound("vs_nbloodm1_hi");
        else
            PlaySound("vs_nprostf1_hi");
      ActionPlayAnimation(ANIMATION_FIREFORGET_GREETING);
     }
     ActionPlayAnimation(ANIMATION_LOOPING_SIT_CHAIR, 1.0, 20.0);
     SetLocalInt(oNPC,"SIT",1);
     DelayCommand(17.0, SetLocalInt(oNPC,"SIT",0));
    }
    /* Персонаж сидит на точке "PV_" + тэг персонажа.
       Hужно подставить заниженный стул Z=-0.25
       Сидит и пьет, если интеллект больше 17, то читает. */

    Теперь скрипт для официанта в таверне. Нужно создать Хозяина таверны, которому будет официант возвращать «выручку», а также нужен хоть один клиент. Число клиентов не ограничено, но можно и ограничить, проставив нужный тег официанта, где первая цифра тега и будет количеством клиентов. Это может пригодиться, если в таверне несколько официантов и их Хозяев. Обслуживаться будут только ближайшие к официанту клиенты, и «выручка» тоже будет относиться ближайшему Хозяину. Сам скрипт:

    //:://///////////////////////
    //:: СКРИПТ ОФИЦИАНТA
    //:: Слот: OnHeartBeat
    //:: Created By: Gennady
    //:://///////////////////////
    void GoToObject(object oTarget, object oPC)
    {
    if (GetIsObjectValid(oTarget))
     {
      AssignCommand(oPC, ActionForceMoveToObject(oTarget, FALSE, 1.0, 20.0));
      AssignCommand(oPC, ActionSpeakString("Получите ваш заказ."));
      AssignCommand(oPC, ActionPlayAnimation(ANIMATION_FIREFORGET_READ));
      AssignCommand(oPC, ActionPlayAnimation(ANIMATION_LOOPING_GET_MID, 1.0, 2.0));
      AssignCommand(oPC, ActionWait(4.0));
     }
    }
    void WalkToObject(object oTarget,vector vFace)
    {
     object oWP = GetNearestObjectByTag("WP_"+GetTag(oTarget));
     SetLocalInt(oTarget,"MARSH",1);
     AssignCommand(oTarget, ClearAllActions());
     AssignCommand(oTarget, SetFacingPoint(vFace));
     DelayCommand(2.0, AssignCommand(oTarget, ActionSpeakString("Спасибо!")));
     DelayCommand(3.0, AssignCommand(oTarget, 
     ActionPlayAnimation(ANIMATION_FIREFORGET_DRINK)));
     DelayCommand(20.0, SetLocalInt(oTarget,"MARSH",FALSE));
     if(oWP == OBJECT_INVALID) return;
     float fFas = GetFacing(oWP);
     DelayCommand(10.0, AssignCommand(oTarget, 
     ActionForceMoveToObject(oWP, FALSE, 0.0, 8.0)));
     DelayCommand(19.0, AssignCommand(oTarget, SetFacing(fFas)));
    }
    void main()
    {
    object oPC = OBJECT_SELF;
    object oTarget = GetNearestObjectByTag("HOZYAIN");
    object oPos;
    vector vFace = GetPosition(oPC);
    float fTime = 30.0;
    int iP = 1, iPN;
    string sTag;
    
    if(GetArea(GetFirstPC()) != GetArea(OBJECT_SELF)) return; // если ПС нет в локе
    //================  МАРШРУТ ====================================================
    if(GetLocalInt(oPC, "MARSH") != 1)
    {
     int iMax = StringToInt(GetStringLeft(GetTag(oPC), 1));
     AssignCommand(oPC, ClearAllActions());
     oPos = GetNearestObjectByTag("POSIT_"+IntToString(iP), oPC);
     while (GetIsObjectValid(oPos))
     {
      iPN++;
      GoToObject(oPos, oPC);
      if(iMax == iPN) break;
      iP++;
      oPos = GetNearestObjectByTag("POSIT_"+IntToString(iP), oPC);
     }
     fTime = iPN*17.0+fTime;
     SetLocalInt(oPC,"MARSH",1);
     DelayCommand(0.3, SetCommandable(FALSE, oPC)); // заблокировать очередь
     AssignCommand(oPC, ActionMoveToObject(oTarget, FALSE));
     AssignCommand(oPC, ActionSpeakString("Получите выручку!"));
     AssignCommand(oPC, ActionPlayAnimation(ANIMATION_LOOPING_GET_MID, 1.0, 2.0));
     AssignCommand(oPC, ActionSpeakString("Я свободна!"));
     AssignCommand(oPC, ActionPlayAnimation(ANIMATION_FIREFORGET_VICTORY1));
     DelayCommand(fTime, SetCommandable(TRUE, oPC)); // разблокировать очередь
     DelayCommand(fTime, SetLocalInt(oPC,"MARSH",FALSE));
    }
    //---------------- проверки каждый раунд ---------------------------------------
    oPos = GetNearestObject(OBJECT_TYPE_CREATURE);
    sTag = GetTag(oPos);
    if (GetStringLeft(sTag, 6)=="POSIT_" && GetLocalInt(oPos, "MARSH") != 1
        && GetDistanceBetween(oPos, oPC) < 2.0)
      WalkToObject(oPos, vFace);
    if (GetDistanceBetween(oTarget, oPC) < 3.0 && GetLocalInt(oPC, "MARSH") == 1)
    {
      AssignCommand(oTarget, ClearAllActions());
      AssignCommand(oTarget, SetFacingPoint(vFace));
      SetCommandable(TRUE, oPC); // разблокировать очередь
      if(GetLocalInt(oTarget, "MARSH_HOZ") == 1)
    switch (Random(3)+1)
    {
     case 1:
     AssignCommand(oPC, SpeakString("Ох, и жаркий денек!"));
     AssignCommand(oTarget, SpeakString("Давай работай!"));
     AssignCommand(oTarget, ActionPlayAnimation(ANIMATION_FIREFORGET_SALUTE));
     break;
     case 2:
     AssignCommand(oPC, SpeakString("Давай пива и рыбу, есть еще заказ."));
     AssignCommand(oTarget, SpeakString("Бойко торгуем!"));
     AssignCommand(oTarget, 
     ActionPlayAnimation(ANIMATION_FIREFORGET_PAUSE_SCRATCH_HEAD));
     break;
     case 3:
     AssignCommand(oPC, SpeakString("Ноги уже не держат..."));
     AssignCommand(oTarget, SpeakString("Вперед и с песней!"));
     AssignCommand(oTarget, ActionPlayAnimation(ANIMATION_FIREFORGET_VICTORY2));
     break;
    }
    else
     {
      SetLocalInt(oTarget,"MARSH_HOZ",1);
      DelayCommand(15.0,SetLocalInt(oTarget,"MARSH_HOZ",FALSE));
     }
    }
    }
    /*
       Обязательно должен быть Хозяин официанта с тегом HOZYAIN
       Клиент должен иметь начало тега POSIT_ + 1,2,3...
       Клиент может иметь точку возврата с тегом WP_ + тег клиента
       Если первый символ тега официанта цифра, то это число клиентов
       Если буква, то обслужит всех клиентов в области
       Время обслуживания одного клиента 17 секунд
    */

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

    //:://///////////////////////////////////////////////////
    //:: NPC - СТАТУЯ
    //:: Слот: OnHeartBeat
    //:: Слот: OnSpellCastAt стоит скрипт: statuya_del
    //:: File name: statuya_attak
    //:: Created By: Gennady
    //:://///////////////////////////////////////////////////
    // ***** СНЯТЬ ВСЕ ЭФФЕКТЫ *****
    void DelEffectOnPC(object oObject)
    {
     effect eOne = GetFirstEffect(oObject);
     while (GetIsEffectValid(eOne))
     {
      RemoveEffect(oObject, eOne);
      eOne = GetNextEffect(oObject);
     }
    }
    /////////////////////////////////////////////////////////
    void main()
    {
     object oNPC = OBJECT_SELF;
     object oPC = GetFirstPC();
     effect eEffect = GetFirstEffect(oNPC);
     effect e49i = EffectVisualEffect(VFX_IMP_DISPEL); // Голубое облако
     effect e135 = SupernaturalEffect(EffectPetrify()); // Окаменение
     string sTag = GetTag(oNPC);
     string sStrL = GetStringLeft(sTag, 3); // Три первых левых буквы
    
     if(GetLocalInt(oNPC, "ATAKA") == 1)
     {ExecuteScript("nw_c2_default1", oNPC); return;}
    
      if(GetArea(oPC)==GetArea(oNPC) && GetDistanceBetween(oPC,oNPC)< 7.0)
       {
        if (GetTag(oNPC) == "STATUYA") return; // если просто СТАТУЯ
        if (sStrL == "ZIP") return;
        if (Random(10) < 5) return; // Случайность
        SetPlotFlag(oNPC, FALSE); // Снимаем  галку сюжет
        DelEffectOnPC(oNPC);
        if (sStrL != "NET")
        {
         // Делаем фракцию враждебный
         ChangeToStandardFaction(oNPC, STANDARD_FACTION_HOSTILE);
         DelayCommand(1.0, AssignCommand(oNPC, ActionAttack(oPC, TRUE)));
        }
        else
        DelayCommand(1.0, ActionStartConversation(oPC)); // диалог персонажа
        ApplyEffectToObject(DURATION_TYPE_INSTANT, e49i, oNPC);
        SetLocalInt(oNPC,"ATAKA",1);
        return;
       }
    if (GetEffectType(eEffect) != EFFECT_TYPE_PETRIFY)
     {
      SetPlotFlag(oNPC, FALSE); // сюжет
      DelayCommand(1.0,ApplyEffectToObject(DURATION_TYPE_PERMANENT,e135,oNPC));
      DelayCommand(3.0, SetPlotFlag(oNPC, TRUE)); // сюжет
     }
    }
     // Если тег = "STATUYA", то это просто статуя.
     // Если тэг начинается с "ZIP", то можно оживить.
     // Если другой, то статуя оживет, и атакует.
     // Если тэг начинается с NET, то начнется диалог с дружественным NPC.
    //:://////////////////////////////////
    //:: NPC - СТАТУЯ
    //:: Cлот: OnSpellCastAt
    //:: File name: statuya_del
    //:: Created By: Gennady
    //:://////////////////////////////////
    void main()
    {
     object oNPC = OBJECT_SELF;
     string sTag = GetTag(oNPC);
     string sStrL = GetStringLeft(sTag, 3); // Три первых левых буквы
     effect eStatue = EffectPetrify();
     int nSpell = GetLastSpell();
    
    if(nSpell == SPELL_DISPEL_MAGIC || nSpell == SPELL_GREATER_DISPELLING ||
       nSpell == SPELL_MORDENKAINENS_DISJUNCTION || nSpell == SPELL_STONE_TO_FLESH)
      {
       // Снимем эффект статуи со скриптом statuya_attak на ХБ
       if (sStrL == "ZIP")
       {SetLocalInt(oNPC,"ATAKA",1); return;}
       SetPlotFlag(oNPC, FALSE); // сюжет
       DelayCommand(0.05, ApplyEffectToObject(DURATION_TYPE_PERMANENT,
       SupernaturalEffect(EffectPetrify()), oNPC));
       DelayCommand(0.06, SetPlotFlag(oNPC, TRUE)); // сюжет
      }
    }

    Если вам необходимо чтобы ночью некоторые персонажи отдыхали, т.е. спали, то вот вам два скрипта… Один с эффектом сна, значит с персом нельзя будет говорить. Другой с анимацией и возможностью разбудить перса и поговорить с ним. Сон будет ночью, ну можно и задать свое время сна, т.е. значение int T

    //:://////////////////////////////////////////////////
    //:: СКРИПТ ДЛЯ СПЯЩИХ (МОЖНО ГОВОРИТЬ)
    //:: File name: de_sleep
    //:: Слот: OnHeartBeat 
    //:://////////////////////////////////////////////////
    void main()
    {
     object oSelf = OBJECT_SELF;
     if(GetArea(GetFirstPC()) != GetArea(oSelf)) return; // если ПС нет в локе
    
     int T = GetTimeHour();
     effect eSL = EffectVisualEffect(VFX_IMP_SLEEP);
     if (GetIsInCombat(oSelf))  return;
     if (IsInConversation(oSelf))  return;
     if(T>=7 && T<21) // день
     {
      ExecuteScript("nw_c2_default1", oSelf);
      SetLocalInt(oSelf,"SLEEP_ANIM",0);
      return;
     }
      AssignCommand(oSelf, ClearAllActions(TRUE));
      if(GetLocalInt(oSelf,"SLEEP_ANIM")==0)
      {
       if(d2()==1) SetLocalInt(oSelf,"SLEEP_ANIM",1);
       else        SetLocalInt(oSelf,"SLEEP_ANIM",2);
      }
      if(GetLocalInt(oSelf,"SLEEP_ANIM")==1)
     ActionPlayAnimation(ANIMATION_LOOPING_DEAD_FRONT, 1.0, 10.0);
      else
     ActionPlayAnimation(ANIMATION_LOOPING_DEAD_BACK, 1.0, 10.0);
     ApplyEffectToObject(DURATION_TYPE_INSTANT, eSL, oSelf);
    }
     /* Для корректной работы, у ходячих удалить скрипты
        nw_c2_default9 и nw_c2_default2 */

    //:://////////////////////////////////////////////////
    //:: СКРИПТ ДЛЯ СПЯЩИХ (НЕЛЬЗЯ ГОВОРИТЬ)
    //:: File name: ef_sleep
    //:: Слот: OnHeartBeat
    //:://////////////////////////////////////////////////
    void main()
    {
      object oSelf = OBJECT_SELF;
      effect eSleep = EffectSleep();
      effect eSL = EffectVisualEffect(VFX_IMP_SLEEP);
    
      if(GetArea(GetFirstPC()) != GetArea(oSelf)) return; // если ПС нет в локе
     if(GetIsDay()) // день
     {ExecuteScript("nw_c2_default1", oSelf); return;}
      AssignCommand(oSelf, ClearAllActions(TRUE));
      ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eSleep, oSelf, 10.0);
      ApplyEffectToObject(DURATION_TYPE_INSTANT, eSL, oSelf); 
    }
     /* Для корректной работы, у ходячих удалить скрипты
        nw_c2_default9 и nw_c2_default2 */
    Обновление: Rambler's Top100