Делаем мод на спавн врагов скриптами в Fallout 4 - часть 1 - основы и вводные
Думала, писать об этом или нет, но попробую. Сейчас я осваиваю скрипты на контролируемый спавн существ игре. В задумке это будет создание группы противников на локациях с некоторой вероятностью после их зачистки. На этом примере я покажу основные принципы и скриптовые команды, что используются в моддинге.
Что конкретно будет делать мод
В планах это система спавна враждебных и мирных существ внутри и вне подземелий с учетом расы местных существ в заданные промежутки времени и с заданной вероятностью. Что-то типа мода SKK Combat Stalkers, но гибче.
Этот мод порой спавнит слишком мощных врагов для моего ребаланса, а потому я разбираю его по кускам, изучаю как он работает и создаю свой аналог с собственными дополнениями. Спавн группы врагов на входе после зачистки локации или после сна на локации, спавн случайных союзников, патрулей, преследователей, может, даже столкновений разных групп.
В рамках этой вводной статьи отработаем основные принципы работы скриптов. Как создавать, как компилировать и какие мелочи учесть, чтобы не убивать часы на поиски их решения.
Подготовка фундамента
Для моддинга нам нужна игра Fallout 4, лучше версии 1.10.163 до некст ген обнов, редактор Creation Kit с поддержкой Unicode (чтобы нормально ел кирилицу) и F4SE, о распространенном баге которого я уже писала ранее. Учитываем эту поправку.
Запускаем Creation Kit, подхватываем базовый файл Fallout4.esm и тыкаем ОК.
ESM - это формат базовых фундаментальных файлов игры и некоторых больших модов, а ESP - это основной ходовой формат модов, нашлепки поверх ESM.
Ждем загрузку, затем в панельке Object Window слева раскрываем список Character, в нем выделяем Quest, а после справа в любом месте нажимаем ПКМ (правая кнопка мыши) и выбираем New в выпадающем списке.
Откроется панелька настройки квеста. Присваиваем ему ID (идентификатор внутри игры), пишем любое название Quest Name, прожимаем чекбокс Start Game Enable (запуск квеста в игре сразу же), присваиваем приоритет Priority (важно для порядка обработки и вывода квестовых реплик) и нажимаем ОК.
После этого сохраняем мод и даем ему название. ЭТО ВАЖНО! После создания нового квеста и присваивания ему ID нужно его сразу же закрыть и сохранить проект, иначе остальные его правки не будут корректно учтены.
Далее заходим в настройки редактора File -> Preferences, протыкиваем стрелочками разделы вправо до упора, выделяем Scripts и присваиваем моду префикс Default Namespace. Благодаря этому все написанные в сессии скрипты будут иметь его в названии и храниться в отдельной папке внутри Fallout 4 / Data / Scripts, что сильно поможет на финальном этапе собирать свои наработки в отдельный архив.
Теперь снова открываем наш квест, входим в раздел Scripts, нажимаем Add, выбираем New Script, тыкаем ОК, в новой панельке вводим название скрипта и снова тыкаем ОК.
Это будет главный скрипт нашего мода, он начнет работать сразу при запуске квеста, и в нем будут происходить все основные расчеты.
База скриптостроения в редакторе
Чтобы начать работать со скриптом, добавим в него переменную игрока. Для этого выделим скрипт и нажмем Properties. В новом окошке прожимаем Add Property, а в следующем выбираем тип переменной Type ->Actor, а в названии Name водим PlayerRef. Проверяем, чтобы была галочка в Const и нажимаем ОК.
В списке появляется переменная PlayerRef, которая заполняется автоматически. Если бы мы ввели просто Player, то редактор бы ее не нашел, и нам пришлось бы вручную делать ссылку на игрока.
Это первый способ добавления переменных. Теперь попробуем второй. Закрываем окошко Properties, нажимаем на скрипте ПКМ и выбираем в выпадающем списке Open in External Editor, откроется обычный текстовый файл, скорее всего, во встроенном блокноте.
Пока у нас есть только заголовок и одна переменная.
Здесь:
- Actor - тип переменной
- Property - отображение переменной в одноименном меню редактора (чтобы задавать там ссылки на сущности)
- PlayerRef - название переменной
- Auto - автоматическое присвоение значения в меню Property
- Const - постоянная (неизменная) переменная
Для контраста давайте добавим другую переменную прямо в блокноте введением текста.
Длинные пробелы здесь - это табуляция кнопкой TAB, их используют для наглядности. Я стараюсь делать так, чтобы все было удобно для меня, но вы можете их вообще не делать.
Это простая переменная типа Int (целое число 1, 4, 25, 367 и т.п.), называемая TimerSpawnerID, равная 1. Она будет отвечать за номер встроенного таймера, поэтому ее не нужно видеть в меню Property (не ставим) и ей не нужно Auto (мы сами задали значение), но оставляем Const, ведь мы не будем его менять (так выделяется меньше памяти ПК на переменную).
Однако, этого мало для принятия изменений редактором. Сохраняем текстовый файл и закрываем. Чтобы наша вписанная переменная начала действовать, нужно произвести компиляцию. Для этого нажимаем на скрипте ПКМ и выбираем Edit Source. В открывшемся окне нажимаем File -> Save (Ctrl + S). Редактор проверит код, и если ошибок нет, выдаст Save Succeeded.
В Fallout 4 скрипты хранятся в двух форматах: открываемый блокнотом .PSC и компилированный для движка .PEX. Именно второй формат читается игрой при запуске и лежит по адресу Fallout 4 / Data / Scripts. Читаемые .PSC же лежат чуть глубже в папке Fallout 4 / Data / Scripts / Source / User.
Компилирование нужно повторять после любых изменений в скриптах через текстовый редактор, иначе изменения не будут приняты игрой. В дальнейшем я не буду заострять на этом внимание.
Базовые функции модов
В движке есть свои встроенные функции, часть из которых является событиями (эвенты Event), что срабатывают по определенным триггерам (когда что-то случается внутри игры). От них, как правило, и начинают работать созданные моддером собственные функции (Function - команды для работы с данными).
Чтобы эвенты работали, нужно их предварительно зарегистрировать, но некоторые встроены в каждый квест изначально, как например, эвент OnQuestInit - событие старта квеста. Вставим его.
В начале эвента и в конце нужно добавлять приписки Event и EndEvent, таковы правила местного языка программирования. Аналогично и для функций, так движок различает, где они начинаются и заканчиваются.
Сейчас эвент пустой, добавим функцию (назовем ее Maintenance) для регистрации двух других эвентов - событий загрузки игры и смены локации (или ячейки открытого мира).
Сразу же добавим вызов функции Maintenance при первом запуске квеста и при каждой загрузке игры. Внутри нее используем встроенную команду RegisterForRemoteEvent для регистрации эвентов.
ВАЖНО! Как вы можете заметить, функция запуска срабатывает лишь раз при первом запуске. Если вы во время него не зарегистрируете событие загрузки игры, то сломаете свой мод, ведь больше не сможете регистрировать события в принципе.
OnQuestInit больше не сработает, а OnPlayerLoadGame вы еще не зарегали. Помянем.
Решение - откатиться до сохранения до запуска мода или отключить мод извне в менеджере модов, запустить игру без мода, сохраниться и включить мод в менеджере снова.
Вы можете заметить необычности в записях выше. Часть событий вызывается, начиная с "Actor.", а другие имеют кучу данных внутри скобочек.
Актор в начале значит, что эвенты срабатывают, ориентируясь на конкретного персонажа, в нашем случае игрока. Поэтому в событии регистрации мы пишем RegisterForRemoteEvent (PlayerRef, "OnPlayerLoadGame"), т.е. зарегать событие загрузки игры для игрока (как будто неписи так умеют делать с: таковы уж правила языка).
В Actor.OnLocationChange(Actor akSender, Location akOldLoc, Location akNewLoc) мы и вовсе видим кашу из букв. Все они - встроенные в событие переменные, которые что-то значат и содержат в момент срабатывания.
Например, здесь:
- Actor akSender - актор, который пересек границу локаций
- Location akOldLoc - локация, которую покинул актор
- Location akNewLoc - локация, в которую пришел актор
Все эти переменные должны быть обязательно указаны при введении события в скрипт. Более подробно об этом можно узнать из вики или официальной документации на сайте Беседки (просто гуглите Fallout 4 + scripts + "название функции или ивента").
Имейте ввиду, что не все события можно вот так вызывать из скрипта квеста применительно к акторам. Некоторые в принципе способны срабатывать только в формате индивидуальных скриптов акторов через раздел Alias квеста.
Об этом вы можете узнать из вики или по ошибкам компилятора. Просто знайте, что так бывает и не бейтесь о стену слишком долго.
Теперь добавим в наш мод циклический таймер, который будет срабатывать через некоторые промежутки времени. Это тоже встроенный эвент, который не срабатывает сразу, а запускается командой StartTimer(20, TimerSpawnerID), где 20 - время срабатывания в секундах, а TimerSpawnerID - идентификатор таймера (его индивидуальный номер).
Вставляем его запуск в Maintenance, чтобы он запустился при запуске игры и при каждой загрузке из-за зарегистрированного события загрузки. Даже если таймер уже работает, команда его просто перезапустит заново.
Внутри таймера помещаем логическую конструкцию, чтобы отделить наш таймер от других. В движке встроенное событие OnTimer(Int aiTimerID) всего одно и срабатывает при истечении любых таймеров, отправляя через собственную переменную aiTimerID идентификатор сработавшего.
Чтобы наш код не срабатывал при истечении сотен внутриигровых таймеров, мы используем проверку "if" (что значит "если"), и сравниваем в ней идентификатор истекшего таймера aiTimerID с нашим введенным в начале статьи идентификатором TimerSpawnerID. Если они совпадают (==), тогда срабатывает код внутри условия, и перезапускает таймер.
Для условий также применительна логика разграничения начала и конца. Пишем if и endif. Кстати, регистр тут не важен. Можете писать и большими и маленькими. Хоть event, хоть IF, каждый модер пишет по-своему.
На этом душная часть завершается. Она необходима как вводная для тез, кто сам захочет в это все втянуться. В будущем я еще буду проговаривать некоторые принципиальные моменты, когда буду вводить дополнительные сущности, но к этим вопросам не вернусь.
Сам мод у меня уже находится на более поздней стадии и существ исправно спавнит, пишу статью задним числом.