fallout.ru

Секреты и приёмы

Я надеюсь этот раздел претерпит серьёзные улучшения в будущем. Присылайте мне примеры Ваших собственных приёмов скриптования, и они будут размещены здесь, если окажутся любопытными.

Стиль безопасного скриптования

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

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

  • Используйте переменные для обеспечения одноразовых конструкций и последовательного выполнения. Из-за принципа «выполнять каждый фрейм», это является единственным условием правильной логики скрипта. Во-первых, сокращайте структуру кода до минимума. Во-вторых используйте конструкцию “if–elseif”, а не отдельные блоки “if”. Конечно, это не всегда возможно и полезно, но зачастую спасает Вас от множества проблем. В-третьих, проверяйте условные конструкции на логику выполнения – правильная организация позволит избежать огрехов. Учтите, что постоянное перепрыгивание с места на место с помощью контрольных переменных равноценно злоупотреблению GOTO в старом добром БЕЙСИКе. И наконец, пишите скрипты потихоньку. Очень часто неправильные скрипты начинали работать, когда я перемещал некоторые функции в отдельный блок условий. Иногда подобные решения могут показаться нелогичными, но, если Вы можете обеспечить выполнение следующего шага, делайте это!

  • Используйте только один стиль. ТЕС КС прощает Вам все Ваши особенности написания синтаксиса. Можно всё писать заглавными буквами, можно строчными, можно вообще хоть как! Однако, какой бы стиль написания Вы ни выбрали, следуйте ему всегда – это поможет Вам же при разборе Ваших скриптов впоследствии.

  • Используйте значащие имена. Ведь имя отражающее назначение переменной делает Ваш скрипт гораздо более удобочитаемым. Если Вы создаёте глобальные переменные, делайте их как можно более уникальными. Да хоть даже ставьте Ваши инициалы в начале их имён! Ведь если пользователь установит другой плагин, в котором будет глобальная переменная с тем же именем – ни этот, ни другой мод работать не будут!

  • Используйте функцию Return с умом! Помните, что весь код после неё будет проигнорирован компилятором! Если Вам кажется, что придётся использовать её в Вашем скрипте часто – лучше завести специальную переменную.

Чистим мод!

При создании модов, Вам, возможно, придётся менять местами некоторые объекты или просто копировать их для собственных целей. Проблема в том, что ТЕС КС «помнит» момент, когда Вы нажимаете кнопку «ОК». Зачастую это может стать причиной определённых неприятностей, в том числе и многочисленных сообщений о возможных ошибках. Перед выпуском в свет Вашего плагина, его необходимо основательно почистить на предмет нежелательных исправлений master-файла.

В меню “File” Конструктора выберите “Data Files…” Затем выделите Ваш мод и щёлкните по кнопке “Details…” Перед Вами появится список всех внесённых Вами в основной файл изменений. Найдите всё то, чего Вы не хотели изменять, и, выделив это, нажмите на кнопку [Delete] на клавиатуре. Это заставит движок проигнорировать подобную деталь. Повторная загрузка и сохранение мода удалит её.

Альтернативный и, наверное, более простой способ – использование утилиты TESAME (TES advanced mod editor – расширенный редактор модов ТЕС), скачать который можно здесь

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

Список индикаторов изменённых объектов:

DIAL – новая или изменённая тема для разговора

INFO – новый ответ или журнальная запись

REFR – новая копия существовавшего до этого объекта, помещённая в мир Морровинда

SOUN – звук

NPC_ – новый или изменённый NPC

CREA – новое или изменённое существо

LIGH – новый или изменённый источник света

LTEX – изменение текстуры ландшафта

PGRD – изменение Искусственного Интеллекта

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

SCRPT – скрипт

MISC – новый или изменённый объект; проверяйте имена в поисках предметов, не связанных с Вашим модом

ACTI – активатор

CONT – контейнер; очень опасно: проверяйте, что Вы изменили только Ваш собственный объект, так как изменение оригинального контейнера повлияет на всю игру.

STAT – статический объект

Будьте аккуратными с очищением диалогов: когда Вы создаёте новый ответ, это также меняет строки, предшествующие и последующие той, которую Вы ввели. Так происходит потому, что все темы хранятся в связанном списке, каждый элемент которого ссылается на два соседних. Такое устройство позволяет движку быстро находить необходимый ответ во время игры. При удаление подобных изменений Вы будете наблюдать сообщения об ошибке, говорящие, что определённый ответ ссылается на абсолютно другой элемент, нежели это описано в Конструкторе. Чтобы устранить эту неприятность, Вы можете перемещать соответствующие фразы вверх–вниз – подобное действие воссоздаст ссылки элементов друг на друга.

Ограничения редактора скриптов

Количество символов: существует ограничение на количество символов в одном скрипте. Оно не должно превышать 32767, что является максимумом для числа типа int, ведь именно в числе того формата хранится длина всех скриптов плагина. Если Вы израсходовали все символы, Вы больше не сможете печатать. Чтобы избежать этого можно делать несколько вещей:

  • Удалить лишние символы
  • Сделать имена переменных короче
  • Попытаться разбить скрипт на части и либо прикрепить их к разным объектам, либо разместить в глобальных скриптах.

Количество строк: максимальное число строк кода может варьироваться от 900 до 1500. Также следует учесть, что это, возможно, лишь строки для компилятора, то есть пустые и закомментированные строки здесь не подсчитываются. При превышении определённого количества, когда Вы попытаетесь сохранить скрипт, Вы получите соответствующее сообщение об ошибке.

Количество условных конструкций: существует также и ограничение на количество конструкций типа “if–elseif”. Насчёт точного числа я не уверен – это может быть как 127, так и 256. Также нельзя превышать и лимит вложенных конструкций – скорее всего, не более 10.

Определение использования книг и свитков

Это удивительно тяжёлое задание, поскольку оно сочетает в себе и функцию OnActivate и OnPCEquip, которые ещё и не совсем правильно работают. Следующий скрипт представляет собой превосходное решение этой проблемы.

Begin BankLetter10

short button
short messageOn
short invoke
short gone
short goneway
short testdist
short PCSkipEquip
short OnPCEquip

set PCSkipEquip to 1
; отключить, если используется
if ( gone == 1 )
            if ( goneway == 1 ) ; открыта не из инвентаря
                        Disable
            else ; открыта из инвентаря
                        startscript BankLetter10Remove
            endif
            set gone to 0
            return
endif

if ( OnActivate == 1 )
            Set messageOn to 2
            set goneway to 1
endif

If ( OnPCEquip == 1)
            Set messageOn to 2
            Set OnPCEquip to 0
            set goneway to 2
endif

if ( messageOn == 0 )
            return
endif

if ( messageOn == 2 )
            MessageBox "Вы хотите призвать письмо с кредитом?" "Да" "Нет"
            Set messageOn to 1
            return
endif

if ( messageOn == 1 )
            set button to GetButtonPressed
            if ( button == 0 )
                        Set invoke to 1
                        Set messageOn to 0
;                       return
            endif
            if ( button == 1 )
                        Activate
                        Set messageOn to 0
                        return
            endif
endif

if ( invoke == 1 )
            PlaySound "Item Gold Up"
            Player->AddItem, Gold_001, 10000
            set gone to 1
            set invoke to 0
endif

End

Как заставить NPC менять оружие

Поскольку функция Equip не совсем правильно работает, единственными двумя способами заставить NPC сменить оружие являются либо его удаление/добавление его, либо смена значения соответствующих навыков.

Пример: в этом скрипте я заставил стражника переключаться между луком и мечом

Begin HBCaravanGuardAI

; этот скрипт вносит усовершенствования в Искусственный Интеллект стражника, уча его менять лук на меч,
; когда игрок подходит ближе

short currentarrows
short storearrows
short doonce

set currentarrows to GetItemCount "arrow of wasting flame"

if ( doonce == 0 )
            set storearrows to currentarrows
endif

if ( GetDistance, Player < 120 )
            set currentarrows to GetItemCount "arrow of wasting flame"
            if ( currentarrows > 0 )
                        RemoveItem "arrow of wasting flame", 1
                        set doonce to 1
            endif
elseif ( GetDistance, Player >= 120 )
            if ( currentarrows < storearrows )
                        AddItem "arrow of wasting flame", 1
            else
                        set doonce to 0
            endif
endif

End

Следующий пример принадлежит Bethesda. В принципе, он делает то же самое, просто изменяя показатели навыков стражника (признаю, что этот метод элегантнее моего :) ):

begin marksmanToggle

short counter
short myMarksman

if ( MenuMode == 1 )
            return
endif

if ( counter < 20 )
            Set counter to counter + 1
            Return
endif

if ( myMarksman == 0 )
            set myMarksman to GetMarksman
endif

if ( GetMarksman > 0 )
            if ( GetDistance Player < 400 )
                        SetMarksman 0
            endif
else
            if ( GetDistance Player > 600 )
                        SetMarksman myMarksman
            endif
endif

;для продвинутых программистов... заставляет ИИ делать то, что он по идее и должен:
;в ближнем бою использовать холодное оружие
;на расстоянии использовать луки, арбалеты и т.д.
;проверка каждые 20 фреймов
;Не влияет на колдовство

End

Альтернативная помощь

Хороший способ почерпнуть много нового для начинающих пользователей – просматривать другие скрипты в поисках примеров применения интересующей Вас функции. К тому же, Вы всегда можете скопировать и вставить понравившиеся фрагменты кода (ctrl–c и ctrl–v соответственно).

Анализ присутствия других модов

Некоторым хочется узнать программно, установлен ли у пользователя ещё какой-либо определённый мод. Это можно определить при следующих обстоятельствах:

  1. существует уникальная переменная
  2. она изменяется с помощью скриптов

Если Вы знаете, что подобная переменная должна присутствовать, создайте её и в своём моде и следите за изменением её значения. Например, Вы знаете, что какой-то чужой плагин содержит глобальную переменную “ForeignGlobal”, Вы также добавляете её к своему моду:

If ( ForeignGlobal != 0 )
            MessageBox "Обнаружен чужой мод!"
endif

К сожалению, подобный метод не работает, когда значение ставится заранее или до выполнения проверки переменная ещё не изменилась.

Настойчивая рекомендация: никогда не устанавливайте значение переменной заранее. Пускай она инициализируется нулём – Вы можете изменить её каком-либо глобальном скрипте, который будет выполняться при загрузке. Так необходимо делать, поскольку в случае, когда другой в другом моде будет также присутствовать переменная с тем же именем, Ваш плагин может отказать в работе. (Например, лучше не давать имя “check”, а добавить к нему хотя бы Ваши инициалы: “VP_check”)

Безопасный запуск глобальных скриптов

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

StartScript "My_Script"

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

Begin Script_launcher

If ( ScriptRunning, My_Script == 0 )
            StartScript, My_Script
            ; MessageBox "Мой скрипт был включён" ; сообщить игроку, если это необходимо
endif

End

Чтобы запустить скрипт в уже начатой давным-давно игре, можно поместить подобные активаторы во всех часто посещаемых местах. Если у Вас установлены такие в Балморе, Вивеке, Садрит Море, Дагон Феле и Калдере – будьте уверены, в скором времени скрипт будет запущен. Также можно прикрепить такой скрипт к какому-нибудь квестовому объекту. Например, в моде, добавляющем банк, он может быть повешен на вывеску – то есть скрипт начнёт выполняться, прежде чем игрок войдёт в здание.

В ТРИБУНАЛЕ подобной проблемы больше не существует – просто добавьте Ваш скрипт в список включаемых автоматически глобальных скриптов. Для этого в меню “Gameplay” выберите “Edit Start Scripts”. После проведения необходимых операций Ваш скрипт будет автоматически запускаться, как только игра стартует.

Экономия быстродействия

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

Если нет необходимости в постоянном выполнении скрипта, добавьте к коду небольшой секундомёр.

Begin My_super_long_script

Short framecounter

If ( framecounter < 10 )
            set framecounter to ( framecounter + 1 )
            Return
Endif

[далее идёт невероятно длинный скрипт]

end

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

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

If ( GetDistance, player > 5000 )
            Return
Endif

Отключайте скрипты, которые Вам уже не нужны. Зачастую после определённого момента времени Вам уже нет нужды в выполнении того или иного кода. Например, при смерти NPC, к которому он был прикреплён. Исходя из этого, напрашивается подобный вариант:

If ( GetDisabled == 1 )
            Return
Endif

If ( GetHealth <= 0 )
            Return
Endif

Завершайте глобальные скрипты. Помните, что они выполняются всё время, пока Вы не примените функцию StopScript, благодаря которой и можно сделать одноразовые глобальные скрипты:

Begin do_once_global_script

[ваш код]

StopScript "do_once_global_script"

End

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

Будьте осторожными с повторяющимися циклами и медленными функциями, типа GetDetected, GetLOS и т.д. Обязательно используйте таймер, дабы не вызывать их слишком часто.

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

If ( MenuMode == 1)
            Return
Endif

Использование звука для определения событий

Эта идея мне кажется очень заманчивой и перспективной, поэтому я повторю её основные положения здесь, несмотря на то, что я уже делал это ранее. Вы можете использовать функцию GetSoundPlaying для определения происшествия недоступных другим образом событий (падение, нахождение неподалёку определённого монстра, определение удара оружием и так далее). В меню “Gameplay”->“Sound menu” Вы можете видеть список всевозможных звуков и их идентификаторов. Учтите, что по каким-то причинам ID “drink” создаёт ошибку, поэтому нет возможности проверить, выпил ли игрок что-нибудь.

Большие битвы

Самый простой способ проводить баталии между неигровыми персонажами – использовать команды, управляющие искусственным интеллектом. Допустим, нам необходимо спровоцировать сражение между игроком, на стороне которого находятся имперские войска, и бандой из Тёмного Братства. Для начала поменяйте значение поля “Fight” (в настройках NPC из Братства) на 100. Таким образом, они начнут битву с игроком, как только тот появится. Затем в каком-либо скрипте Вам нужно будет установить для легионеров следующие настройки искусственного интеллекта:

AIFollow, player, 0,0,0,0,0

Это заставит солдат сражаться на стороне игрока, как только бандиты бросятся в бой. Превосходный способ устраивать большие потасовки!

Нацеленные скрипты

"ID Объекта" -> StartScript "ID Скрипта"

Существует возможность запускать скрипты, как бы прикрепляя их к каким бы то ни было объектам в любой момент времени. Подобные скрипты будут являться и локальными (они всё так же относятся к своим объектам), и глобальными (выполняются постоянно). Подобный способ позволяет заставлять NPC выполнять сложные действия, даже когда игрок находится очень далеко.

Однако, эти скрипты всё-таки отличаются от локальных. Переменные рассматриваются не как локальные. Если остановить, а потом вновь запустить скрипт, все значения переменных будут вновь скинуты на 0. Поэтому Вам придётся вновь устанавливать их вручную.

Я не знаю, что будет, если одновременно прикрепить скрипт к более чем одному объекту. Однако, сомнительно, что это будет иметь положительный эффект.

Таким образом, теперь снимается лимит на количество скриптов, прикреплённых к одному объекту. Теперь сразу несколько из них могут выполняться в одно и то же время, причём один не будет ждать конца выполнения другого. Из-за этого необходимо помнить, что логика скриптов не может идти в двух разных направлениях, даже если это и не вызовет конфликта.

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

Если использовать нацеленные скрипты из поля результата диалога, то они все будут прикрепляться к NPC, с которым говорит игрок. То есть пример “Object_ID -> StartScript "script_name"” работать не будет.

Создание транспортных средств

Выбор объекта

Практически любой объект подойдёт для нашей цели. Однако, выбор наиболее удачного из них серьёзно облегчит задачу. Предпочтение отдаётся маленьким объектам небольшой ширины/высоты. Другой важный параметр – центр объекта, в котором и будет стоять игрок. Это позволит избавиться от большой части работы. Если центр своим положением не подходит к Вашим замыслам, Вы можете импортировать объект в 3DS и там исправить это положение. Если Вы не знаете, как это делается, в Сети лежит множество руководств на эту тему.

Создание/Удаление объектов

Я уверен все, кто хоть раз делал свой мод, знают, что объекты можно передвигать только на определённые расстояния, потому что движок игры работает только с определёнными локациями, неподалёку от игрока. Как только он покидает ту местность, все её объекты как бы замораживаются во времени, растворяясь в воздухе, а когда игрок вновь возвращается – продолжается их активное состояние.

Ключом к перемещению объекта по всему простору мира является создание его новых копий. Каждый раз, когда игрок перемещается между локациями, функция CellChanged принимает значение TRUE. Это и есть подходящий момент для замены одного объекта другим. Однако, здесь существует одна большая проблема. Никогда не создавайте объект из локального скрипта. Ничего не будет работать! Поэтому необходимо использовать глобальный скрипт. В то же время, не забывайте удалять старый объект, иначе рано или поздно появится неимоверное количество его копий. В одно и то же время у Вас должен быть лишь один объект! Вот как этого можно добиться:

;----------------
; Локальный скрипт
;----------------
if ( player->CellChanged == 1 )
            Startscript, "Create_obj_script" ; запускаем глобальный скрипт
            set obj_count to ( obj_count - 1 ) ; глобальная переменная для подсчёта объетов
            Disable
            SetDelete, 1
endif

;----------------
; Глобальный скрипт
;----------------
PlaceAtPC "objectname", 1, 0, 0 ; Также можно использовать функцию PlaceItem
set obj_count to ( obj_count + 1 )
Stopscript "create_obj_script"

По идее, этот пример должен работать, как часы, однако, на деле выдаёт всевозможные ошибки. Дело в том, что мгновенное удаление объекта, создаёт определённые ошибки, особенно пока загружается обширная локация. Поэтому это действие необходимо выполнить с задержкой (лично я использую 1,5 секунды). Итак, посмотрим на код:

if ( player->CellChanged == 1 )
            Startscript, "Create_obj_script" ; запускаем глобальный скрипт
            Disable
            set timer_flag to 1
endif

if ( timer_flag == 1 )
            set timer to ( timer + GetSecondsPassed )
            if ( timer > 1.5 )
                        set obj_count to ( obj_count - 1 )
                        SetDelete, 1
            else
                        return ; остановить выполнение остальной части кода
            endif
endif

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

if ( player->CellChanged == 1 )
            if ( timer_flag == 1 )
                        SetDelete, 1
                        return
            endif
            Startscript, "Create_obj_script"
            Disable
            set timer_flag to 1
endif

if ( timer_flag == 1 )
            set timer to ( timer + GetSecondsPassed )
            if ( timer > 1.5 )
                        SetDelete, 1
            else
                        return
            endif
endif

Падение с объекта

Это довольно просто. Необходимо отменить на игроке привила гравитации. Этого можно достичь с помощью эффекта полёта или «плавания». Это является превосходным решением, однако при такой реализации невозможно проверить, крадётся ли или бежит ли игрок. Вас также может удивить то, что игрок всегда падает с объекта в момент его создания. Так происходит потому что параметры соприкосновения обновляются только когда происходит смена локации. Игрок как бы проходит объект насквозь. Хорошие новости состоят в том, что такое положение дел вполне возможно обойти, используя функции Enable и Disable. Чтобы быть уверенным, что Ваш скакун не сбрасывает своего наездника, используйте эти две функции в скрипте объекта хотя бы раз в один фрейм.

Определение касания

Этот момент может стать настоящей головной болью. Объекты (в данном случае статические объекты и активаторы) не могут соприкасаться с другими объектами. Лишь живым существам дарована такая привилегия. Самым простым методом обойти это ограничения является расчёт координат положения игрока и последующее сравнение их с координатами объекта, верхом на котором вы путешествуете. Если у Вас есть более удобный метод, пожалуйста поделитесь им с нами.

Сохранение игры

Если Вы сохраняете игру во время движения, то сохранёнными координатами объекта станут координаты первоначального его положения, когда он только создавался в данной локации. Чтобы позиция скакуна определялась правильно стоит хранить его координаты в глобальных переменных. При загрузке игры сопоставляйте эти данные и при необходимости меняйте положение объекта в соответствии с переменными.

Манекены

Манекены весьма популярны среди людей, жаждущих показать всем свою коллекцию брони – мы можем обнаружить их во множестве модов, добавляющих дома. Давайте разберёмся, как же всё-таки можно создать манекен. Пример иллюстрирует, как можно использовать функции Трибунала, чтобы проверить предметы в инвентаре, исключить возможность малейшего движения манекена, пока на нём что-нибудь надето. Две версии скрипта необходимы для существования манекенов разных полов. Однако невозможно исключить возможность перетаскивания игроком манекена, если у него есть только разнообразные мелкие предметы – они просто-напросто будут потеряны.

Фактически, манекен – это NPC со здоровьем, равным 0. В этой версии скрипта Вы просто можете надевать и снимать одежду и вкладывать ему в руки оружие.

Begin rsn_mannequin_f_script

short button
short questionState
short nEquipType
short nStillEquipped
float fDeleteTimer

SkipAnim ;Это строка необходима, чтобы превратить живого NPC в неподвижного манекена.

if ( menumode == 1 )
            return
endif

if ( GetDisabled == 1 )
            ;Если манекен был отключён, необходимо немного подождать, а затем удалить его копию.
            Set fDeleteTimer to ( fDeleteTimer + GetSecondsPassed )
            if ( fDeleteTimer > 5 )
                        SetDelete 1
            endif
            return
endif

if ( OnActivate == 0 )
            if ( questionState == 0 )
                        return
            endif
endif

if ( questionState == 0 )
            MessageBox, "Вооружить манекен", "Передвинуть манекен", "Добавить/Удалить броню"
            set questionState to 1
endif

if ( questionState == 1 )
            set button to GetButtonPressed
            if ( button == 0 )
                        set questionState to 10
            elseif ( button == 1 )
                        set questionState to 0
                        Activate
            endif
endif

if ( questionState == 10 )
            ;Эта секция разбита на 2 блока условных конструкций из-за лимита на их количество.
            Set nStillEquipped to 0
            ;Здесь мы проверяем, одет ли манекен
            Set nEquipType to ( GetWeaponType )
            if ( nEquipType == -1 )
                        ;Здесь мы проверяем, надета ли на манекен какая-либо броня.
                        ;Учтите, что существует 10 разных частей
                        ;брони, поэтому нам придётся проверять каждую индивидуально.
                        Set nEquipType to ( GetArmorType 0 )
                        if ( nEquipType == -1 )
                                    Set nEquipType to ( GetArmorType 1 )
                                    if ( nEquipType == -1 )
                                                Set nEquipType to ( GetArmorType 2 )
                                                if ( nEquipType == -1 )
                                                            Set nEquipType to ( GetArmorType 3 )
                                                            if ( nEquipType == -1 )
                                                                        Set nEquipType to ( GetArmorType 4 )
                                                                        if ( nEquipType == -1 )
                                                                                    Set nEquipType to ( GetArmorType 5 )
                                                                                    if ( nEquipType != -1 )
                                                                                                Set nStillEquipped to 1
                                                                                                ;Установить на 1, если всё равно
                                                                                                ;надета какая-то часть брони
                                                                                    endif
                                                                        else
                                                                                    Set nStillEquipped to 1
                                                                        endif
                                                            else
                                                                        Set nStillEquipped to 1
                                                            endif
                                                else
                                                            Set nStillEquipped to 1

                                                endif
                                    else
                                                Set nStillEquipped to 1
                                    endif
                        else
                                    Set nStillEquipped to 1
                        endif
            else
                        Set nStillEquipped to 1
            endif

            if ( nStillEquipped != 1 ) ;Продолжаем, только если на манекене ничего нет.
                        Set nEquipType to ( GetArmorType 6 )
                        if ( nEquipType == -1 )
                                    Set nEquipType to ( GetArmorType 7 )
                                    if ( nEquipType == -1 )
                                                Set nEquipType to ( GetArmorType 8 )
                                                if ( nEquipType == -1 )
                                                            Set nEquipType to ( GetArmorType 9 )
                                                            if ( nEquipType == -1 )
                                                                        Set nEquipType to ( GetArmorType 10 )
                                                                        If ( nEquipType == -1 )
                                                                                    ;Показываем этот вопрос, только если
                                                                                    ;на манекене нет ни оружия, ни брони.
                                                                                    MessageBox "Вы удалили все вещи с манекена?", "Да", "Нет"
                                                                        else
                                                                                    Set nStillEquipped to 1
                                                                        endif
                                                            else
                                                                        Set nStillEquipped to 1
                                                            endif
                                                else
                                                            Set nStillEquipped to 1
                                                endif
                                    else
                                                Set nStillEquipped to 1
                                    endif
                        else
                                    Set nStillEquipped to 1
                        endif
            endif
            ;переходим на следующий уровень обработки действий игрока
            set questionState to 20
endif

if ( questionState == 20 )
            if ( nStillEquipped != 1 )
                        set button to GetButtonPressed
            else

                        ;На манекен по-прежнему что-то надето,
                        ;поэтому мы предупредим игрока о том, что поднимать манекен
                        ;нельзя.
                        MessageBox "Вы не сняли обмундирование"
                        Set button to 1 ;показывает, что манекен одет
            endif

            if ( button == 0 )
                        set questionState to 0
                        ;Отключаем этот манекен и создаём новый, не заботясь о потере вещей
                        ;Если манекен часто перемещают, то функция SetDelete могла бы стать неплохим решением.
                        Disable
                        ;Это тот самый предмет, к которому прикреплён следующий скрипт.
                        ;Он необходим для создания новой копии
                        ;манекена, когда игрок захочет поставить его на землю.
                        ;Для манекена с женской фигурой этот предмет нужно будет заменить на соответствующий
                        player->addItem, "_rsn_man_f_holder", 1
                        playSound "Item Misc Up"
            elseif ( button == 1 )
                        ;У манекена всё ещё есть какие-то предметы,
                        ;о чём мы узнаём либо из проверки, либо по ответу игрока
                        set questionState to 0
                        Activate
            endif
endif

end

Следующий скрипт прикрепляется к объекту, добавленному в инвентарь игрока, когда тот захочет его перенести. При попытке бросить этот предмет будет появляться новая копия манекена. И не забывайте, что всё предметы, прикреплённые к нему, при этом пропадут.

Begin rsn_man_f_holder_script

short OnPCDrop
float fDeleteTimer

if ( GetDisabled == 1 )
            ;если объект был отключён, необходимо немного подождать и отключить его копию.
            Set fDeleteTimer to ( fDeleteTimer + GetSecondsPassed )
            if ( fDeleteTimer > 5 )
                        SetDelete 1
            endif
            return
endif

if ( OnPCDrop == 1 )
            Disable
            ;Заставляем наш манекен – на самом деле, всего лишь стоящий труп, - находиться в вертикальном положении
            PlaceAtPC, "_rsn_mannequin_female", 1, 0, 0
            Set OnPCDrop to 0
endif

end

Она смотрит на меня?

Ниже приведён пример замечательного скрипта, позволяющего определить, смотрит ли NPC в сторону игрока.

Begin PCLookAtMe

float fPCX
float fPCY
float fPCAngle
float fdx
float fdy
float fRatio
short sPCLookAtMe

set sPCLookAtMe to 1

;Здесь также можно использовать функцию GetLOS
;Однако, я так и не смог заставить её работать, как следует… Правда, я не то чтобы очень пытался.
;если игрок очень далеко

if ( GetDistance, Player > 8000 )
            set sPCLookAtMe to 0
else
            ;тригонометрия
            ;здесь просто расчёт того, является ли направление игрока видимым для NPC
            ;используется угол 45 градусов

            set fPCX to ( player->GetPos, X )
            set fPCY to ( player->GetPos, Y )
            set fPCAngle to ( player->GetAngle, Z )
            set fdx to GetPos, X
            set fdy to GetPos, Y
            set fdx to ( fdx - fPCX )
            set fdy to ( fdy - fPCY )
            set fRatio to ( fdx / fdy )
            if ( fdx > 0 )
                        if ( fdy > 0 )
                                    if ( fRatio > 1 )
                                                if ( fPCAngle < -45 )
                                                            set sPCLookAtMe to 0
                                                endif
                                    else
                                                if ( fPCAngle < -90 )
                                                            set sPCLookAtMe to 0
                                                endif
                                                if ( fPCAngle > 135 )
                                                            set sPCLookAtMe to 0
                                                endif
                                    endif
                        else
                                    if ( fRatio < -1 )
                                                if ( fPCAngle < 0 )
                                                            if ( fPCAngle > -135 )
                                                                        set sPCLookAtMe to 0
                                                            endif
                                                endif
                                    else
                                                if ( fPCAngle < 45 )
                                                            if ( fPCAngle > -90 )
                                                                        set sPCLookAtMe to 0
                                                            endif
                                                endif
                                    endif
                        endif
            else
                        if ( fdy > 0 )
                                    if ( fRatio < -1 )
                                                if ( fPCAngle > 45 )
                                                            set sPCLookAtMe to 0
                                                endif
                                    else
                                                if ( fPCAngle > 90 )
                                                            set sPCLookAtMe to 0
                                                endif
                                                if ( fPCAngle < -135 )
                                                            set sPCLookAtMe to 0
                                                endif
                                    endif
                        else
                                    if ( fRatio > 1 )
                                                if ( fPCAngle > 0 )
                                                            if ( fPCAngle < 135 )
                                                                        set sPCLookAtMe to 0
                                                            endif
                                                endif
                                    else
                                                if ( fPCAngle > -35 )
                                                            if ( fPCAngle < 90 )
                                                                        set sPCLookAtMe to 0
                                                            endif
                                                endif
                                    endif
                        endif
            endif
endif

if ( sPCLookAtMe == 0 )
            ;делать что-то, когда игрок не смотрит
endif

End

Последовательность анимации

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

If ( menumode==1 )
            return
endif

if ( doOnce==0 )
            "Collision wall2"->disable ;Невидимая стена №2
            "Collision wall3"->disable ;Невидимая стена №3
            "Collision wall4"->disable ;Невидимая стена №4
            set doOnce to 1
endif

if ( doOnce==1 )
            "Collision wall1"->moveworld X 800
            messagebox "Двигается"
            if ( "Player"->getPos Z < 570 )
                        set doOnce to 2
                        set playxx to "Player"->getPos X
                        set playyy to "Player"->getPos Y
                        set playzz to "Player"->getPos Z
                        "Collision wall2"->enable
                        "Player"->position –114679 –4119 590 90
            endif
endif

if ( doOnce==2 )
            "Collision wall2"->moveworld X 800
            messagebox "Двигается"
            if ( "Player"->getPos Z < 570 )
                        set playxx to "Player"->getPos X
                        set playyy to "Player"->getPos Y
                        set playzz to "Player"->getPos Z
                        "Collision wall3"->enable
                        "Player"->position –112634 –4119 590 90
                        set doOnce to 3
            endif
endif

if ( doOnce==3 )
            "Collision wall3"->moveworld X 200
            "Collision wall3"->moveworld Y –800
            if ( "Player"->getPos Z < 570 )
                        set doOnce to 4
                        set playxx to "Player"->getPos X
                        set playyy to "Player"->getPos Y
                        set playzz to "Player"->getPos Z
                        "Collision Wall4"->enable
                        "Player"->position –112126 –6150 590 90
            endif
endif

if ( doOnce==4 )
            "Collision wall4"->moveworld X 600
            "Collision wall4"->moveworld Y –450
            if ( "Player"->getPos Z < 570 )
                        set doOnce to 5
                        set playxx to "Player"->getPos X
                        set playyy to "Player"->getPos Y
                        set playzz to "Player"->getPos Z
            endif
endif

if ( doOnce==5 )
            stopscript ELDQ_visualforbattle
endif

end ELDQ_visualforbattle