Делаем мод на спавн врагов скриптами в Fallout 4 - часть 3 - добавляем возможность настройки мода

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

А ведь можно было просто спавнить их за спиной игрока в случайные промежутки времени 
А ведь можно было просто спавнить их за спиной игрока в случайные промежутки времени 

Предыдущая статья:

Что делаем дальше

Добавим переменные с указанием количества заспавненных существ в виде диапазона мин/макс и вынесем их в меню МСМ для возможности ручной корректировки. Также вставим вспомогательные сообщения, которые будут всплывать в углу экрана, сообщая нам о ходе работы скриптов.

Добавляем переменные

Для начала добавим кучку переменных. Ниже я оградила их от тех, что уже есть с помощью комментария - нечитаемого кодом текста внутри скрипта. Он отделяется символом ";". Правда, я не уверена, можно ли писать комментарии на кириллице, не будет ли проблем при компиляции.

Scriptname KP_DES:MainQuestScript extends Quest Actor Property PlayerRef Auto Const ActorBase Property LvlRaider Auto Const Mandatory Bool SpawnerEligibleLoc Bool SpawnerAlreadySpawn FormList Property DES_SpawnMarkersFL Auto Const Mandatory Int TimerSpawnerID = 1 Const Keyword Property LocTypeClearable Auto Const ;новые переменные ниже Bool Property ShowDebug Auto Conditional Bool Property uiSpawnerEligibleLoc Auto Conditional Bool Property uiSpawnerAlreadySpawn Auto Conditional Int Property setTimerSpawnerDelay Auto Conditional Int Property setSpawnerMinDistance Auto Conditional Int Property setSpawnerChanceToSpawn Auto Conditional Int Property setSpawnerMinActors Auto Conditional Int Property setSpawnerMaxActors Auto Conditional

ShowDebug - переменная-переключатель, с помощью которой мы будем контролировать вывод сплывающих сообщений.

Две переменные uiSpawnerEligibleLoc и uiSpawnerAlreadySpawn дублируют одноименные, но с припиской ui в начале. Так я помечаю дубликаты переменных для вывода игроку в меню МСМ на случай, если игрок их начнет переключать ручками и что-нибудь сломает.

Переменные с припиской set - это те, которые я помечаю как переключатели внешних настроек. Их мы перенесем в МСМ меню, и поэтому у них есть приписка Conditional в конце. Она говорит, что эти переменные доступны для обращения вне этого скрипта, например, для использования в системе условий редактора.

После внесения компилируем скрипт и настраиваем для них стартовые значения в меню Properties, иначе все будет по нулям.

Ставьте значения как здесь
Ставьте значения как здесь

Вносим переменные в функции

Для начала, везде, где мы вызываем таймер заменяем 20 секунд на переменную setTimerSpawnerDelay. Теперь мы сможем контролировать длительность таймера одной правкой, а не заменой 20 на новое число в каждом месте кода по отдельности.

... ;например, как здесь Function Maintenance() RegisterForRemoteEvent(PlayerRef, "OnPlayerLoadGame") RegisterForRemoteEvent(PlayerRef, "OnLocationChange") StartTimer(setTimerSpawnerDelay, TimerSpawnerID) EndFunction ...

Теперь переходим в функцию спавна. Заменяем в условии минимальное безопасное расстояние 1000 на setSpawnerMinDistance. Так мы сможем вручную его подбирать, не перезапуская игру через меню МСМ.

... Function SpawnActors() if (SpawnerEligibleLoc == true && SpawnerAlreadySpawn == false) ... int distance = 0 While (i < FoundSpawnMarkers.length) ; <-- ВОТ ЗДЕСЬ distance = PlayerRef.GetDistance(FoundSpawnMarkers[i]) as int if (distance > setSpawnerMinDistance) SpawnMarker = FoundSpawnMarkers[i] endIf i += 1 EndWhile ... endif EndFunction ...

Куда сильнее мы поменяем сам спавн. Организуем его через знакомый по прошлому уроку цикл while, который будет длиться в количестве шагов, равном actorsNum. При объявлении в эту переменную мы записываем случайное значение встроенной командой Utility.RandomInt, а в скобках запишем диапазон рандома от setSpawnerMinActors до setSpawnerMaxActors.

Хорошей практикой при вызове рандома будет на всякий случай заранее проверять, чтобы переменная Min была меньше чем Max через условие if.

Но здесь пока не буду перегружать этим скрипт.
... Function SpawnActors() ... if (SpawnMarker != none) i = 1 int actorsNum = Utility.RandomInt(setSpawnerMinActors, setSpawnerMaxActors) While (i <= actorsNum) Actor a0 = SpawnMarker.PlaceActorAtMe(LvlRaider) Utility.Wait(0.1) a0.Moveto(SpawnMarker, Utility.RandomInt(-100, 100), Utility.RandomInt(-100, 100), abMatchRotation = true) a0.MoveToNearestNavmeshLocation() i += 1 EndWhile if (ShowDebug) debug.notification("[DES] "+ actorsNum +" actors spawned!") endif SpawnerAlreadySpawn = true uiSpawnerAlreadySpawn = SpawnerAlreadySpawn endif endif EndFunction ...

Аналогичный рандом я добавила в команду a0.Moveto(SpawnMarker, Utility.RandomInt(-100, 100), Utility.RandomInt(-100, 100), чтобы каждый заспавненный рейдер был смещен немного в сторону от маркера. Так их появление будет выглядеть естественнее и исключать риска застревания друг в друге.

Еще одно добавление в код - это строчки:

... if (ShowDebug) debug.notification("[DES] "+ actorsNum +" actors spawned!") endif ...

Здесь мы как раз проверяем переключатель ShowDebug, и если он равен true, то используем встроенную команду debug.notification, которая выводит на экран всплывающее сообщение, находящееся в скобочках, слева сверху.

В нем прописной текст заключается в кавычки "ТЕКСТ", но можно выводить и числовые значения, прямо указывая название переменной, а знак "+" используется для разделения переменных и текста друг от друга.

Вот так сообщение выглядит в игре
Вот так сообщение выглядит в игре

Под конец припишем uiSpawnerAlreadySpawn = SpawnerAlreadySpawn, чтобы наша справочная переменная отмечала момент спавна на локации.

Далее откроем код события смены локации и внесем в него правки. Объявим переменную chance и сразу внесем в нее случайное значение командой RandomInt(). Вставим его в условие и объявим локацию непригодной для спавна, если оно не будет выполняться.

Да, это та же самая команда.

Если напишем RandomInt(), то возвращает число от 0 до 100.

Если напишем RandomInt(х, у), то возвращается число от х до у.

Допускается двойственное написание
... Event Actor.OnLocationChange(Actor akSender, Location akOldLoc, Location akNewLoc) if ( akNewLoc.HasKeyword(LocTypeClearable) && !akNewLoc.IsCleared() ) int chance = Utility.RandomInt() if (setSpawnerChanceToSpawn > chance) SpawnerEligibleLoc = true else SpawnerEligibleLoc = false endif else SpawnerEligibleLoc = false endIf SpawnerAlreadySpawn = false uiSpawnerAlreadySpawn = SpawnerAlreadySpawn uiSpawnerEligibleLoc = SpawnerEligibleLoc EndEvent ...

Только что мы добавили механику шанса на спавн врагов на локации. Управлять им также будем через меню МСМ. И напоследок в конце двумя строчками приравняем справочные ui переменные к фактическим.

Внедряем поддержку меню МСМ

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

После установки в папке Fallout 4/Data появится папка МСМ, заходим в ее подпапку Сonfig и создаем свою, внутри которой создадим текстовый файл config формата .json.

Название папки не важно, а файл .json можете создать, скопировав из другого мода, либо создайте текстовый документ формата .txt и замените формат вручную.

Для этого надо отобразить форматы файлов в настройках системы (2 скрин ниже).

Откроем файл конфига .json обычным блокнотом или его аналогом. Он заполняется по определенным правилам и содержит в себе ссылки на переменные и даже функции модов. Тело и структура файла выглядит так:

{ "modName": "KP DES", "displayName": "KP-DES: Динамический спавнер врагов", "minMcmVersion": 2, "content": [ {}, {}, {} ] }

Заполнение первых трех строчек не особенно важно, они показывают название и справочную информацию.

Внутри фигурных скобок { } могут находиться различные наполнители от текста и пробелов до переключателей, слайдеров настроек и кнопок вызова функций.

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

{ "modName": "KP DES", "displayName": "KP-DES: Динамический спавнер врагов", "minMcmVersion": 2, "content": [ {}, ... {}, {} ], "pages": [ {"pageDisplayName": "вкладка 1", "content": [ {}, ... {}, {} ] }, ... {"pageDisplayName": "вкладка 2", "content": [ {}, ... {}, {} ] }, {"pageDisplayName": "вкладка х", "content": [ {}, ... {}, {} ] } ] }

Обратите ВНИМАНИЕ! Сходные элементы разграничиваются между собой запятыми ",", а последний элемент ее не имеет после закрытой "}".

Эта ошибка часто приводит к тому, что МСМ отказывается открывать файл, правьте.

Добавим парочку элементов в фигурные скобки { }. Текстовые боксы поддерживают команды выравнивания, размера шрифта и т.п., поэтому в тексте допустимы не все символы, ошибка чтения всего файла может быть, например, из-за безобидной простановки ":".

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

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

Главное, не забывайте делать бэкап перед удалением блоков.

Блок spacer - это пробел между блоками, а slider - бегунок для настройки значения. В нем вы указываете минимальное min, максимальное max и шаг step, на который бегунок будет перемещаться. Последний блок switcher - это переключатель ВКЛ/ВЫКЛ, удобный для переменных типа bool (true/false).

В элементе text пишется название блока для отображения, а в help - вспомогательный справочный текст, который отобразится в подписи при выделении.

{ "modName": "KP DES", "displayName": "KP-DES: Динамический спавнер врагов", "minMcmVersion": 2, "content": [ {"text": "<p align='center'><font size='24'>Kabachok Production - Dynamic Enemy Spawner ver. 0.1 (KP-DES)</p></font>", "type": "text", "html": true }, {"text": "<font size='16'>Мод спавнит врагов внутри подземелья после его зачистки игроком.</font>", "type": "text", "html": true }, {"type": "spacer", "numLines": 1 }, {"text": "Время цикла таймера", "type": "slider", "help": "С какой периодичностью локация проверяется на условия для спавна (в секундах)", "valueOptions": {"min": 5, "max": 120, "step": 5, "sourceType": "PropertyValueInt", "sourceForm": "KP-DynamicEnemySpawner.esp|1000F99", "propertyName": "setTimerSpawnerDelay"} } ], "pages": [ {"pageDisplayName": "Отладка", "content": [ {"text": "Показывать системные сообщения", "type": "switcher", "help": "Показывать сообщения для разработчика", "valueOptions": {"sourceType": "PropertyValueBool", "sourceForm": "KP-DynamicEnemySpawner.esp|1000F99", "propertyName": "ShowDebug"} } ] } ] }

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

В sourceType пишется тип переменной (в данном случае int), в sourceForm - ссылка на файл мода формата .esp и идентификационный номер квеста со скриптом (или на id глобальной переменной, например). Как правило, самый первый созданный в моде квест имеет номер 1000F99, поэтому вы можете часто его видеть в разных файлах. Допускается еще запись 0F99, так как первые 3 цифры - это порядок загрузки мода в менеджере.

Конкретный id можно подсмотреть в программе FO4Edit
Конкретный id можно подсмотреть в программе FO4Edit

Наконец, в propertyName записывается название конкретной изменяемой переменной, глобальной переменной или вызываемой функции.

... "sourceType": "PropertyValueInt", "sourceForm": "KP-DynamicEnemySpawner.esp|1000F99", "propertyName": "setTimerSpawnerDelay"} ...

Вы можете при желании в одном файле МСМ обращаться к переменным разных модов. Все ссылки на данные прописаны именно в этих 3 строчках.

Еще бывает дополнительный файл setting, но мы его пока трогать не будем.

А теперь заполним файл до конца по аналогии:

{ "modName": "KP DES", "displayName": "KP-DES: Динамический спавнер врагов", "minMcmVersion": 2, "content": [ {"text": "<p align='center'><font size='24'>Kabachok Production - Dynamic Enemy Spawner ver. 0.1 (KP-DES)</p></font>", "type": "text", "html": true }, {"text": "<font size='16'>Мод спавнит врагов внутри подземелья после его зачистки игроком.</font>", "type": "text", "html": true }, {"type": "spacer", "numLines": 1 }, {"text": "Время цикла таймера", "type": "slider", "help": "С какой периодичностью локация проверяется на условия для спавна (в секундах)", "valueOptions": {"min": 5, "max": 120, "step": 5, "sourceType": "PropertyValueInt", "sourceForm": "KP-DynamicEnemySpawner.esp|1000F99", "propertyName": "setTimerSpawnerDelay"} }, {"type": "spacer", "numLines": 1 }, {"text": "Минимальное расстояние до игрока", "type": "slider", "help": "Расстояние до маркера, ближе которого спана не будет (100 ~ 1 метр)", "valueOptions": {"min": 500, "max": 10000, "step": 500, "sourceType": "PropertyValueInt", "sourceForm": "KP-DynamicEnemySpawner.esp|1000F99", "propertyName": "setSpawnerMinDistance"} }, {"type": "spacer", "numLines": 1 }, {"text": "Шанс спавна акторов", "type": "slider", "help": "С какой вероятностью в локации будет спавн акторов", "valueOptions": {"min": 0, "max": 100, "step": 5, "sourceType": "PropertyValueInt", "sourceForm": "KP-DynamicEnemySpawner.esp|1000F99", "propertyName": "setSpawnerChanceToSpawn"} }, {"text": "Минимальное число акторов", "type": "slider", "help": "Сколько акторов спавнить", "valueOptions": {"min": 1, "max": 10, "step": 1, "sourceType": "PropertyValueInt", "sourceForm": "KP-DynamicEnemySpawner.esp|1000F99", "propertyName": "setSpawnerMinActors"} }, {"text": "Максимальное число акторов", "type": "slider", "help": "Сколько акторов спавнить", "valueOptions": {"min": 1, "max": 10, "step": 1, "sourceType": "PropertyValueInt", "sourceForm": "KP-DynamicEnemySpawner.esp|1000F99", "propertyName": "setSpawnerMaxActors"} } ], "pages": [ {"pageDisplayName": "Отладка", "content": [ {"groupControl": 1, "text": "Показывать системные сообщения", "type": "switcher", "help": "Показывать сообщения для разработчика", "valueOptions": {"sourceType": "PropertyValueBool", "sourceForm": "KP-DynamicEnemySpawner.esp|1000F99", "propertyName": "ShowDebug"} }, {"type": "spacer", "numLines": 1 }, {"groupCondition": 1, "text": "Локация подходит для спавна", "type": "switcher", "help": "Это незачищенное подземелье", "valueOptions": {"sourceType": "PropertyValueBool", "sourceForm": "KP-DynamicEnemySpawner.esp|1000F99", "propertyName": "uiLocationEnemyType", "propertyName": "uiSpawnerEligibleLoc"} }, {"groupCondition": 1, "text": "На локации уже был спавн", "type": "switcher", "help": "Враги уже были заспавнены или спавн отменен", "valueOptions": {"sourceType": "PropertyValueBool", "sourceForm": "KP-DynamicEnemySpawner.esp|1000F99", "propertyName": "uiSpawnerAlreadySpawn"} } ] } ] }

Здесь все в целом по аналогии, но обращу внимание на строчки "groupControl": 1 и "groupCondition": 1 в последнем разделе. С помощью них можно скрывать часть элементов от отображения.

Если значение переменной в блоке с записью groupControl равно 0 (false), тогда в данном разделе будут скрыты все блоки с записью groupCondition. Цифра "1" в конце этих строк говорит номер этой группы для сокрытия.

Т.е. "groupControl": 1 скрывает все блоки "groupCondition": 1, а "groupControl": 2 скроет все блоки "groupCondition": 2, и т.п. Только имейте виду, что МСМ несколько деревянный, и для смены отображения скрываемого может потребоваться переключить разделы или даже перезайти в МСМ меню.

Внутри МСМ все будет выглядеть так
Внутри МСМ все будет выглядеть так

На этом сделаю паузу. В следующем уроке научимся определять тип врагов в подземелье, отображать его в Отладке МСМ меню текстом и спавнить соответствующих противников, а не одних лишь рейдеро.

8
1
1