Делаем мод на спавн врагов скриптами в Fallout 4 - часть 5 - добавляем пакеты поведения

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

Рандомная картинка патруля из тырнетов
Рандомная картинка патруля из тырнетов

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

Варианты спавна существ

Разместить существ в игровом мире через редактор Creation Kit можно в трех формах:

  • Actor - непосредственно заспавненное существо, имеющее конкретную внешность и параметры.
  • ActorBase - набор параметров для Actor, шаблон, выбирающий внешность и параметры при его создании.
  • LeveledActor - контейнер системы автолевел, который исходя из уровня игрока, решает какой ActorBase заспавнить при загрузке локации.

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

Actor Property PlayerRef Auto Const Actor Property DN024_LvlRaiderBoss Auto Const ActorBase Property LvlRaider Auto Const Mandatory LeveledActor Property LCharRaiderMixedEquipment Auto Const Function SpawnActor() actor a0 = none a0 = PlayerRef.PlaceActorAtMe(DN024_LvlRaiderBoss) EndFunction Function SpawnActorBase() actor a0 = none a0 = PlayerRef.PlaceActorAtMe(LvlRaider) as actor EndFunction Function SpawnLeveledActor() PlayerRef.PlaceAtMe(LCharRaiderMixedEquipment) EndFunction

В данном примере DN024_LvlRaiderBoss - это именной босс рейдеров Джаред, поэтому он имеет переменную Actor. При спавне рандомного LvlRaider типа ActorBase в конце делаем приписку "as actor", чтобы корректно перенести его в переменную а0 типа Actor.

При спавне LeveledActor используем команду PlaceAtMe, ведь технически это контейнер и не родственен типам переменных Actor или ActorBase. По этой же причине мы не можем напрямую и вытянуть из него ссылку на заспавненного актора, чтобы занести в а0. Это можно сделать иначе, например, через сканирование всех существ на локации (но об этом в другой раз).

Для дальнейших целей будем использовать переменную типа ActorBase.

Пакеты поведения

Для движения по локациям существа используют пакеты поведения (Package), прописанные в квестах, настройках локаций и/или в их ActorBase. Эти пакеты имеют шаблоны настроек типа Follow (следовать за кем-то), Sandbox (брождение по области), Travel (путешествие куда-либо) и т.п.

В отдельной панельке Flags можно настроить через чекбоксы разные аспекты поведения, вроде, скорости ходьбы, реакции на тела, возможность плавания, а через вкладку Сondition настроить условия, когда пакеты действуют. Например, утром бродить по рынку по одному пакету, днем сидеть в баре по второму, а ночью спать в кровати по третьему.

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

Для начала создадим новый пакет поведения и настроим его на шаблон Sandbox и поменяем пару настроек во вкладке Flags.

Делаем мод на спавн врагов скриптами в Fallout 4 - часть 5 - добавляем пакеты поведения

Далее сделаем копию рейдера, снимем галочку в Templates напротив AI Packages, чтобы можно было настраивать ее вручную. И добавим новосозданный пакет в список пакетов.

И не забудем заменить LvlRaider на DES_LvlRaider в скрипте основного квеста. Теперь заспавненные акторы рейдеров будут бродить по округе после спавна. То же самое можно сделать и с другими существами.

Пакет движения к сундуку босса

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

Откроем наш квест и перейдем во вкладку Alias. В ней содержатся сущности, с которыми квест взаимодействует. Тут могут быть ссылки на персонажей, с которыми квест добавляет диалоги, особые локации и квестовые предметы, на которые ставится маркер задания.

Для начала создадим Alias игрока, он нужен, чтобы на нем можно было создать маркер цели следования для неписей.

Теперь создадим Alias для типового объекта маркера XMarker, который при создании должен поместиться поверх Alias игрока.

ВАЖНО! поставить у маркера чекбокс Optional, что скажет игре, что этот Alias допускается не заполнять при создании, иначе квест может не запуститься, если игра по каким-то причинам не сможет его заполнить.

Мы сделаем это ручками через скрипт.

Теперь создадим пока пустой Alias под заспавненных существ. Для одного конкретного существа или предмета, например, Престона Гарви, персонажа игрока или маркера используется New Reference Alias, но для набора нескольких сущностей применяется его подвид Collection.

Здесь также отметим Optional, чтобы квест заработал. Опционально можно добавить любые предметы в панельке Alias Inventory - они сразу же поместятся в инвентарь любой сущности, что будет включена в этот Alias.

Так можно будет в игре убедиться, что назначение существа в Alias Collection прошло успешно.

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

Через панельку Alias Packages Data создадим квестовый пакет поведения с шаблоном Patrol и в качестве центра для патрулирования укажем Alias маркера цели. Это доступно, потому что этот пакет привязан к нашему конкретному квесту.

Делаем мод на спавн врагов скриптами в Fallout 4 - часть 5 - добавляем пакеты поведения

Протыкиваем везде ОК и сохраняем мод. Добавленный через квест пакет поведения всегда будет приоритетнее того, что прописан в самом Actor.

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

Создаем список, а потом ищем контейнеры и перетягиваем в него
Создаем список, а потом ищем контейнеры и перетягиваем в него

Манипулируем Alias-ами через скрипты

Осталось только все наладить кодом. Откроем его и вначале объявим переменные Alias и списка контейнеров:

Scriptname KP_DES:MainQuestScript extends Quest ... LocationRefType Property BossContainer Auto Const ReferenceAlias Property EnemyPatrolMarker Auto RefCollectionAlias Property EnemyPackAlias Auto Mandatory STATIC Property XMarker Auto Const ...

Я все никак не могу вспомнить, что такое приписка Mandatory и зачем она нужна. Походу, я ее добавляю, когда при компиляции кода редактор ругается и требует ее добавления.

Как вспомню, обязательно напишу :р

В конце объявляем также объект XMarker типа STATIC. Это статический объект - физический маркер, который мы будем создавать в нужном месте и заносить в Alias EnemyPatrolMarker. Сам наш Alias маркера изначально пустой, нам нужен существующий в игре объект, чтобы его заполнить.

Добавляем сразу функцию очистки EnemyPackAlias, чтобы не переполнять ее бесконечным числом существ:

... Function ClearAliases() int i = 0 if (EnemyPackAlias.GetCount() > 0) While ( i < EnemyPackAlias.GetCount() ) ObjectReference a0 = EnemyPackAlias.GetAt(i) EnemyPackAlias.RemoveRef(a0) i += 1 EndWhile endif EndFunction ...

Команда GetCount запрашивает число сущностей, занесенных внутрь Alias Collection, и, если они там есть (больше 0), то мы пробегаемся по всему списку через while, заносим их референс в а0 командой GetAt и удаляем из Alias Collection командой RemoveRef.

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

... Function SpawnActors() ObjectReference SpawnMarker = none ObjectReference[] FoundSpawnMarkers = PlayerRef.FindAllReferencesOfType(DES_SpawnMarkersFL as form, 10000) int i = 0 int distance = 0 While (i < FoundSpawnMarkers.length) distance = PlayerRef.GetDistance(FoundSpawnMarkers[i]) as int if (distance > setSpawnerMinDistance) SpawnMarker = FoundSpawnMarkers[i] endIf i += 1 EndWhile if (SpawnMarker != none) ClearAliases() ;ВОТ ЗДЕСЬ ОЧИЩАЕМ АЛИАС Utility.wait(1) i = 1 int actorsNum = Utility.RandomInt(setSpawnerMinActors, setSpawnerMaxActors) actor a0 = none While (i <= actorsNum) a0 = SpawnMarker.PlaceActorAtMe(ActorType) as actor EnemyPackAlias.AddRef(a0) ;ВОТ ЗДЕСЬ ЗАНОСИМ АКТОРА В АЛИАС Utility.Wait(0.1) i += 1 EndWhile SpawnerAlreadySpawn = true uiSpawnerAlreadySpawn = SpawnerAlreadySpawn Utility.wait(1) EnemyPackAlias.EvaluateAll() ;ОБНОВЛЯЕМ ПАКЕТЫ EndFunction ...

После спавна каждого актора командой AddRef добавляем свеженьких существ в Alias Collection. Им в карман добавится предмет, что мы указали в нем, а также к ним применится квестовый пакет поведения.

ВАЖНО! Команды серипта для внесения сущностей в Alias работают только в скриптах, находящихся в том же квесте, что и сам Alias.

Оперирование с Alias-ами других квестов может не сработать

Иногда игра подтупливает и не применяет пакет сразу, поэтому в конце кода используем команду EvaluateAll ко всему Alias Collection, чтобы принудительно обновить их расписание дествий.

Последнее, что нужно сделать, это передвинуть наш Alias маркер цели для действующего пакета. Для начала нужно найти ближайший к игроку Boss Container. Принцип поиска используем тот же, что и для поиска перехода между локациями:

... Function SpawnActors() ObjectReference SpawnMarker = none ObjectReference[] FoundSpawnMarkers = PlayerRef.FindAllReferencesOfType(DES_SpawnMarkersFL as form, 10000) ... SpawnerAlreadySpawn = true uiSpawnerAlreadySpawn = SpawnerAlreadySpawn ;НАЧИНАЕТСЯ ОТСЮДА ObjectReference PatrolMarker = PlayerRef.PlaceAtMe(XMarker) EnemyPatrolMarker.ForceRefTo(PatrolMarker) ObjectReference[] FoundBossContainers = PlayerRef.FindAllReferencesOfType(DES_Boss_Container as form, 10000) if (FoundBossContainers.length > 0) i = 0 While (i < FoundBossContainers.length) PatrolMarker.MoveTo(FoundBossContainers[i], 0, 0, 0, True) i += 1 EndWhile endif ;ЗАКАНЧИВАЕТСЯ ЗДЕСЬ Utility.wait(1) EnemyPackAlias.EvaluateAll() ;ОБНОВЛЯЕМ ПАКЕТЫ endif EndFunction ...

В начале мы командами...

ObjectReference PatrolMarker = PlayerRef.PlaceAtMe(XMarker) EnemyPatrolMarker.ForceRefTo(PatrolMarker)

...создаем поверх игрока маркер XMarker и следом заносим его в Alias маркер цели. С этого момента заспавненные существа будут двигаться к маркеру, расположенному рядом с позицией игрока.

Далее мы сканируем окружение в поисках босс контейнера и сравнивая предметы с контейнерами из созданного списка. Если такой находится, то мы перемещаем XMarker к нему командой MoveTo, и существа движутся туда же. Если же контейнер не найдется, то существа пойдут в сторону позиции игрока на момент спавна.

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

Логично поставить его на игроке, как единственной, всегда существующей сущности.

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

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

44
22
22