Справочник мододела

Как создать нового персонажа, дать ему вещи, поставить на место, задать поведение, диалог и квест — и какие значения бывают у каждого параметра. Дополнение к документации мода «Бар», для тех, кто ещё не делал моды.

Списки собраны по стандартным файлам Зова Припяти. IX-Ray 1.6 — доработанный движок, поэтому что-то может отличаться. Пометка (проверьте) значит: сверьте с оригинальными файлами или логом. Если не уверены — найдите рабочий пример в оригинальной игре и копируйте его структуру.

Ничего не найдено.

01Где смотреть полные списки

Архив мода содержит только изменённые файлы. Всё остальное лежит в gamedata.db0, db1 и т. д. — распакуйте их (например, STALKER DB Unpacker) в отдельную папку для справки.

Золотое правило: чтобы изменить оригинальный файл, скопируйте его в gamedata мода по тому же пути и правьте копию. Оригиналы не трогайте.
Что узнатьГде смотреть
Оружие, патроны, насадкиconfigs\weapons\
Еда, медицина, артефакты, броняconfigs\misc\ (items.ltx, artefacts.ltx, outfit.ltx)
Торговляconfigs\misc\trade\
Родительские секции NPC и мутантовconfigs\creatures\stalkers.ltx, monsters.ltx
Группировки и отношенияconfigs\creatures\game_relations.ltx
Карточки, профили, диалогиconfigs\gameplay\character_desc_*.xml, npc_profile.xml, dialogs*.xml
Логика стандартных NPCconfigs\scripts\<уровень>\
Условия и эффектыscripts\xr_conditions.script, xr_effects.script
Состояния и анимацииscripts\state_lib.script, state_mgr_animation_list.script
Тексты · модели · голосаconfigs\text\rus\ · meshes\actors\ · sounds\characters_voice\

02Как искать ошибки

Лог игры — главный инструмент. После запуска откройте его и ищите: ERROR, Can't, Cannot, script, stack trace, attempt to, имя вашего NPC или секции.

Сообщение в логеТипичная причина
Can't find section ...опечатка в имени секции или файл не подключён через #include
attempt to index a nil valueобращение к объекту, которого нет (NPC не создан, не онлайн, опечатка)
attempt to call a nil valueфункции с таким именем нет в модуле
cannot open ... .ltxневерный путь в custom_data или файла нет в gamedata
XML ... errorнезакрытый тег или лишний корневой тег
invalid vertex / NPC стоитlevel_vertex_id не соответствует позиции

Консоль — клавиша ~. Полезно: g_god 1, g_unlimitedammo 1, demo_record 1 (свободная камера, выход — Esc).

Координаты и номера вершин: временно добавьте функцию в скрипт и вызовите, стоя в нужной точке.

function print_my_pos()
  local p = db.actor:position()
  printf("POS %.2f,%.2f,%.2f lvid=%d gvid=%d", p.x, p.y, p.z,
    db.actor:level_vertex_id(), db.actor:game_vertex_id())
end

03Создание нового NPC

Пример: торговец «Ржавый», id my_npc_4. Один и тот же id используется во всех шагах.

Шаг 1. Секция спавна

configs\creatures\spawn_sections.ltx — в конец файла

[my_npc_4]:stalker
character_profile = my_npc_4
spec_rank = veteran
community = stalker
custom_data = scripts\my_npc_4.ltx

Шаг 2. Профиль

configs\gameplay\npc_profile.xml — в конец, перед закрывающим корневым тегом

<character id="my_npc_4">
  <class>my_npc_4</class>
</character>

Шаг 3. Карточка

configs\gameplay\character_desc_my.xml

<specific_character id="my_npc_4" team_default="1">
  <name>Ржавый</name>
  <icon>ui_npc_u_stalker_neutral_1</icon>
  <map_icon x="0" y="0"></map_icon>
  <bio></bio>
  <class>my_npc_4</class>
  <community>stalker</community>
  <terrain_sect>stalker_terrain</terrain_sect>
  <rank>300</rank>
  <reputation>0</reputation>
  <money min="1000" max="2000" infinitive="0"/>
  <visual>actors\stalker_neutral\stalker_neutral_2</visual>
  <snd_config>characters_voice\human_02\stalker\</snd_config>
  <crouch_type>-1</crouch_type>
  <start_dialog>my_npc_4_dialog</start_dialog>
  <supplies>
    [spawn] \n
    bread = 2 \n
    wpn_pm = 1 \n
    ammo_9x18_fmj = 3
  </supplies>
</specific_character>
  1. Диалог — в dialogs_my.xml (раздел 08).
  2. Логика — файл configs\scripts\my_npc_4.ltx (раздел 10).
  3. Появление в мире — раздел 12.
  4. Новая игра — сохранения хранят старое состояние мира.
NPC не появился или «пустой»? Проверьте по порядку: совпадают ли имена во всех звеньях; подключён ли файл в system.ltx; нет ли ошибок в логе; не стоит ли инфопорция ..._spawned из старого сохранения; соответствует ли level_vertex_id позиции.

04Секция спавна: ключи, группировки, ранги

Формат: [имя]:родитель, ниже строки ключ = значение. Любой ключ родителя можно переопределить.

КлючЧто делаетЗначения
:stalkerродительская секция: модель, здоровье, ИИ, зрениеstalker для обычных; другие — в stalkers.ltx, monsters.ltx
character_profileкакой профиль взятьid из npc_profile.xml
spec_rankранг для движкаnovice, regular, veteran, master
communityгруппировка: «свой/чужой»таблица ниже
custom_dataфайл логики NPCscripts\имя.ltx (от папки configs)
story_idуникальный ярлык для get_story_objectуникальное значение, не повторять

Группировки (community)

ЗначениеКто этоЗначениеКто это
stalkerодиночкиmonolith«Монолит»
banditбандитыzombiedзомбированные
dolg«Долг»csky«Чистое небо»
freedom«Свобода»renegadeренегаты
killerнаёмникиgreh«Грех»
army / militaryвоенные (проверьте)isgISG
ecologучёныеtrader, arena_enemy, actorторговцы, враги арены, игрок

Мутанты — группы monster_* (monster_predatory_day, monster_vegetarian, monster_zombied_day). Таблица отношений — в game_relations.ltx.

Ранги

В карточке — число <rank>, в спавне — слово. Они должны быть согласованы: novice 0–299, regular 300–599, veteran 600–899, master 900+.

05Профиль и карточка персонажа

Профиль — «мост» между секцией спавна и карточкой: <character id> = то, что пишется в character_profile; <class> = id карточки.

ТегЧто делаетЗначения
<name>имя над NPC и в диалогетекст или id из string_table
<icon>портрет в диалоге и торговлеui_npc_u_<группа>_<тип>_<номер>; ищите в configs\ui\
<terrain_sect>«места обитания» для симуляцииstalker_terrain и аналоги
<reputation>репутацияотрицательная — плохая, положительная — хорошая
<money min max infinitive>деньгиinfinitive="1" — бесконечные (торговец)
<visual>3D-модель, путь в meshes\ без расширенияпапки stalker_neutral, stalker_bandit, stalker_dolg… + номер варианта
<snd_config>озвучкаcharacters_voice\human_0N\<группа>\
<crouch_type>поведение в укрытии-1 решает ИИ, 0 не приседать, 1 приседать
<start_dialog>диалог при разговореid диалога; пусто — разговор невозможен
<actor_dialog>доп. диалоги, которые начинает игрокid диалога (можно несколько)

Формат supplies

  • Первая строка всегда [spawn] \n. Каждая строка, кроме последней, кончается текстом \n, а не настоящим переносом.
  • Количество можно опустить (будет 1). Вероятность — число от 0 до 1: имя = 2, 0.5.
  • Несуществующее имя предмета — ошибка в логе, предмет не создастся.

06Предметы: выдать, положить, забрать

КатегорияПримеры имён
Оружиеwpn_pm, wpn_ak74, wpn_ak74u, wpn_abakan, wpn_sig550, wpn_bm16, wpn_svd
Патроныammo_9x18_fmj, ammo_5.45x39_fmj, ammo_12x70_buck
Насадкиwpn_addon_scope, wpn_addon_silencer, wpn_addon_grenade_launcher
Гранатыgrenade_f1, grenade_rgd5
Еда и напиткиbread, kolbasa, conserva, vodka, energy_drink
Медицинаbandage, medkit, medkit_army, medkit_scientic, antirad
Броня · артефактыnovice_outfit, stalker_outfit · af_medusa, af_cristall, af_night_star, af_blood

Точные имена сверяйте с распакованными файлами игры.

local a = db.actor
-- выдать игроку бинт
alife():create('bandage', a:position(), a:level_vertex_id(), a:game_vertex_id(), a:id())
-- положить медаптечку на пол (без id владельца)
alife():create('medkit', a:position(), a:level_vertex_id(), a:game_vertex_id())
-- 30 патронов игроку
alife():create_ammo('ammo_9x18_fmj', a:position(), a:level_vertex_id(), a:game_vertex_id(), a:id(), 30)
-- забрать артефакт
local item = a:object('af_medusa')
if item then alife():release(alife():object(item:id()), true) end

Есть ли предмет: db.actor:object('имя') вернёт предмет или nil. Деньги: db.actor:give_money(5000). Ящик: вместо id владельца передайте id объекта inventory_box.

07Диалоги

Файл dialogs_my.xml: корень <game_dialogs>, внутри <dialog id> с одним <phrase_list>. Имя файла без .xml дописывается в [dialogs] files в system.ltx.

Как читать дерево

  • Фраза id="0" — всегда первая реплика NPC (id менять нельзя).
  • <next> — следующие реплики другой стороны; фраза без <next> завершает диалог.
  • Игроку показываются все варианты с выполненными условиями; NPC отвечает первым подходящим — запасной вариант ставьте последним.
  • Все условия фразы складываются через «И».
ТегЧто делает
<text>текст реплики или id из string_table (тогда работает перевод)
<next>какая фраза может идти следом (можно несколько)
<has_info> / <dont_has_info>показать, только если у игрока есть / нет инфопорции
<give_info> / <disable_info>выдать / забрать инфопорцию при выборе
<precondition>модуль.функция, возвращает true/false
<action>модуль.функция при выборе фразы
<script_text>текст формирует скрипт (проверьте)

Текст: перенос — \n; цвет — %c[255,255,0,0] (красный), вернуть обычный — %c[default].

<dialog id="my_npc_4_dialog">
  <phrase_list>
    <phrase id="0">
      <text>Чего тебе?</text>
      <next>ask_job</next>
      <next>bye</next>
    </phrase>
    <phrase id="ask_job">
      <text>Нет ли работы?</text>
      <dont_has_info>my_job_taken</dont_has_info>
      <next>job_answer</next>
    </phrase>
    <phrase id="job_answer">
      <text>Принеси бинтов — заплачу.</text>
      <give_info>my_job_taken</give_info>
      <action>my_quest.some_function</action>
    </phrase>
    <phrase id="bye">
      <text>Бывай.</text>
      <action>dialogs.break_dialog</action>
    </phrase>
  </phrase_list>
</dialog>

Функции для precondition и action пишутся в любом .script мода и получают двух собеседников:

function has_art(first_speaker, second_speaker)
  return db.actor:object('af_medusa') ~= nil
end
function finish_art(first_speaker, second_speaker)
  local item = db.actor:object('af_medusa')
  if item then alife():release(alife():object(item:id()), true) end
  db.actor:give_money(5000)
end

Порядок собеседников зависит от места вызова — напечатайте first_speaker:name() в лог. Запуск диалога: кнопка «использовать» (нужны start_dialog и use = true), meet_dialog в meet-секции, либо из скрипта db.actor:run_talk_dialog(npc, true).

08Инфопорции и задания

Инфопорция — именованный флажок «игрок знает/сделал X». Описывается в info_my.xml: <info_portion id="my_job_taken"></info_portion>. Подключение — [info_portions] files в system.ltx. Схема имён: <квест>_<состояние>.

ГдеПроверить «есть»«нет»ВыдатьЗабрать
Диалог<has_info><dont_has_info><give_info><disable_info>
Логика .ltx{+имя}{-имя}%+имя%%-имя%
Luahas_alife_info('имя')not has_alife_info(...)db.actor:give_info_portion('имя')db.actor:disable_info_portion('имя')

Задания в КПК

local t = CGameTask()
t:set_id('my_task_art_medusa')
t:set_title(game.translate_string('my_task_art_medusa_name'))
t:set_description(game.translate_string('my_task_art_medusa_text'))
t:set_priority(2)
t:set_type(task.additional)   -- или task.storyline
db.actor:give_task(t, 0, true)
-- состояние:
db.actor:set_task_state(task.completed, 'my_task_art_medusa', 0)

Состояния: task.in_progress, task.completed, task.fail. Сигнатуры различаются в сборках — сверяйтесь с task_manager.script и my_quest.script.

09Логика NPC

Файл configs\scripts\*.ltx подключается ключом custom_data. Комментарии — ;. Одна строка — один параметр. Мод пока использует только active, meet, trade.

Ключ [logic]Что делает
activeстартовая секция, например walker@a
meetmeet@имя или no_meet
trademisc\trade\имя.ltx
on_info, on_timerглобальные переходы из любой секции
on_death{условие} %эффект% при смерти
combat_ignore, hit, wounded, danger, abuseреакции на бой, попадание, ранение, опасность, оскорбления
gather_items, help_wounded, corpse_detectiontrue / false

Секция называется схема@метка: до @ — тип поведения, после — любое имя (walker@a, walker@b).

СхемаЧто делаетГлавные параметры
walkerходит по путиpath_walk, path_look, def_state_standing, def_state_moving1/2/3, team, use_camp
remarkстоит, анимация, смотрит на цельanim, target, snd, snd_anim_sync, tips
camperзасада на постуpath_walk, path_look, radius, shoot (always/none/terminal), sniper
kampу костраcenter_point, radius, no_aim
sleeperспитpath_main, path_look, wakeable
patrolпатруль группойformation (back/line/around), move_type, commander
followerидёт за лидеромleader

Мутанты: mob_walker, mob_home, mob_jump, mob_remark, mob_combat. Физика: ph_*. Зоны: sr_*. Полные списки — в scripts\xr_*.script, ph_*.script, sr_*.script.

Переходы между секциями

on_info   = {=dist_to_actor_le(4)} remark@front
on_signal = arrived | remark@pause
on_timer  = 8000 | walker@b
; несколько вариантов — сработает первый подходящий
on_info   = {+a} sec1, {-a} sec2, sec3
КлючКогда срабатывает
on_infoвыполнено условие в { }
on_signalсхема послала сигнал: arrived, anim_end, sound_end, theme_end
on_timer / on_game_timerпрошло N мс / игровых секунд
on_actor_dist_le / _geигрок ближе / дальше N метров
on_actor_inside / _outsideигрок внутри / снаружи зоны
on_hit, on_deathNPC получил попадание / умер

Внутри { }: +имя есть инфопорция, -имя нет, =функция(арг) вернула true, !функция(арг) вернула false, ~25 — с вероятностью 25 %. Внутри % %: +имя выдать, -имя забрать, =функция(арг) выполнить.

Частые условия: dist_to_actor_le(N), actor_in_zone(зона), actor_has_item(имя), actor_enemy, actor_friend, npc_community(группа), is_day, is_night, see_actor, killed_by_actor. Частые эффекты: give_item, spawn_object, send_tip, play_sound, teleport_actor, give_task, destroy_object. Полные списки — в xr_conditions.script и xr_effects.script.

Готовые образцы

Стоит на месте и смотрит на игрока

[logic]
active = remark@stand
meet = meet@my
[remark@stand]
anim = guard
target = story|actor
[meet@my]
use = true
allow_break = true

Ходит между точками

[logic]
active = walker@a
meet = meet@my
[walker@a]
path_walk = my_route_a
def_state_moving1 = walk
def_state_standing = guard
on_signal = arrived | remark@pause
[remark@pause]
anim = guard
on_timer = 8000 | walker@a
[meet@my]
use = true

Охранник в засаде · Спящий

[camper@post]
path_walk = my_post_walk
path_look = my_post_look
radius = 20
shoot = always
sniper = false

[sleeper@sleep]
path_main = my_bed_walk
path_look = my_bed_look
wakeable = true

Каждый путь нужно предварительно создать (раздел 11). Торговля работает, если указан trade и в meet не отключён trade_enable; для разговора нужны use = true и непустой start_dialog.

10Состояния и анимации

def_state_* в walker и anim в remark — имена состояний из state_lib.script. Состояние = поза + оружие + анимация + движение.

Как добавить новое состояние

  1. В state_mgr_animation_list.script скопируйте запись (my_idle) и переименуйте: into — вход, out — выход, idle — основная, rnd — случайные, prop.rnd — вероятность случайной в процентах, prop.maxidle / prop.sumidle — ограничения основной в секундах.
  2. В state_lib.script скопируйте состояние и задайте поля.
  3. В поле animation укажите имя из шага 1.
  4. Используйте имя в логике NPC (anim = моё_состояние).
Имена анимационных файлов (например bar_1_idle_0) должны существовать в наборе анимаций модели (meshes\actors\...\*.omf). Иначе NPC будет стоять в T-позе. Новые анимации требуют отдельных инструментов для .omf.
ПолеЗначения
weaponnone, strapped (за спиной), unstrapped (в руках, опущено), fire, sniper_fire, drop, nil
movementmove.stand, move.walk, move.run, nil
mentalanim.free (спокоен), anim.danger, anim.panic, nil
bodystatemove.standing, move.crouch, nil
animstateвспомогательное состояние; в моде nil
animationимя записи из state_mgr_animation_list.script или nil

11Расстановка: координаты, пути, спавн

ПараметрЧто значит
position = x,y,zточка в мире; y — высота (вывод позиции — раздел 02)
yaw / directionугол поворота; подбирается опытным путём
level_vertex_idячейка навигационной сетки; по ней ходят NPC
game_vertex_idвершина игрового графа; для одноуровневого мода обычно 0

Правило: level_vertex_id должен соответствовать position. Номер от другой точки — NPC появится не там, провалится или будет стоять.

Способ А. Скриптом при старте

Проще, не нужна пересборка all.spawn. В my_spawn.script есть список EXTRA_NPCS — добавляйте запись по образцу. Из Lua вручную:

if not has_alife_info('my_npc_4_spawned') then
  local pos = vector():set(133.12, -4.82, 20.98)
  local obj = alife():create('my_npc_4', pos, 8929, 0)
  db.actor:give_info_portion('my_npc_4_spawned')
end

Инфопорция защищает от повторного создания при каждой загрузке. Так можно создавать NPC, мутантов, предметы, ящики.

Способ Б. Зашить в карту (all.spawn)

Добавьте блок объекта в spawn/alife_zaton.ltx, пересоберите папку spawn/ в all.spawn (universal_acdc, режим компиляции) и положите результат в gamedata\spawn\. Нужно для ламп, костров, зон, лестниц. Проще всего скопировать соседний блок и менять имя, позицию и вершины; новый блок идёт в конец с очередным номером [N].

КлючЧто значит
section_nameкакая секция создаётся (campfire, inventory_box, предмет…)
nameуникальное имя объекта
position, directionпозиция и поворот
game_vertex_id, level_vertex_idвершины
custom_data, story_idлогика объекта, ярлык для скриптов
character_profile, health, moneyдля NPC

Пути (spawn/way_zaton.ltx)

[my_route_a]
points = p0,p1,p2
p0:name = wp00|sig=arrived
p0:flags = 0x1
p0:position = 133.12,-4.82,20.98
p0:game_vertex_id = 0
p0:level_vertex_id = 8929
p1:name = wp01|t=5000
p1:flags = 0x1
p1:position = 128.07,-4.83,21.07
p1:game_vertex_id = 0
p1:level_vertex_id = 8100
  • [имя] пишется в path_walk, path_look, path_main.
  • После | в имени точки: sig=имя — сигнал при прибытии, t=мс — подождать, a=состояние — поза в точке.
  • flags = 0x1 — обычная точка, менять не нужно. Путь из одной точки допустим.
  • После правки way_zaton.ltx обязательна пересборка all.spawn.

12Шпаргалка по Lua

Комментарий — --. Переменная — local x = 5. Не равно — ~=. Склейка строк — ... Пусто — nil. Функция из my_quest.script вызывается снаружи как my_quest.функция.

Что нужноКак
Игрок · уровеньdb.actor · level.name()
Объект по story_idget_story_object('id') (nil, если не онлайн)
Объект по idlevel.object_by_id(id) (клиентский), alife():object(id) (серверный)
Имя, секция, id, позицияobj:name(), :section(), :id(), :position()
Новая точка · расстояниеvector():set(x,y,z) · a:position():distance_to(b:position())
Создать / удалитьalife():create(...) · alife():release(se_obj, true)
Перевод строки · время (мс)game.translate_string('id') · time_global()
Консольная командаget_console():execute('команда')
Запустить диалог · логdb.actor:run_talk_dialog(npc, true) · printf('текст %s', v)
Подсказка в КПКnews_manager.send_tip(db.actor, 'текст', nil, 'отправитель', 5000) (проверьте)

Где вызываются ваши скрипты. Новый скрипт сам не запустится — нужен вызов: при создании игрока через bind_stalker.script (в моде там вызов my_spawn.spawn_my_npc()), из логики NPC через =функция, из диалогов через <action> и <precondition>.

Серверный и клиентский объект. alife():object(id) есть всегда; level.object_by_id(id) — только если NPC «онлайн» рядом с игроком. Для анимаций и оружия нужен клиентский — всегда проверяйте его на nil.

13Рецепты и чек-листы

Новый торговец

  1. Секция, профиль, карточка как в разделе 03; в карточке <money min="100000" max="100000" infinitive="1"/>.
  2. В логике trade = misc\trade\твой_файл.ltx, в meet — use = true, trade_enable = true.
  3. Файл торговли берите за основу из configs\misc\trade\.
  4. В диалог добавьте фразу, открывающую торговлю (скопируйте из диалога бармена).

Охранник, который не разговаривает

Пустой <start_dialog></start_dialog>; в логике meet = no_meet или use = false; оружие в <supplies>; поведение — remark с anim = guard.

Квест «принеси предмет»

  1. Инфопорции ..._given и ..._done в info_my.xml.
  2. Название и описание в string_table_my.xml.
  3. В my_quest.script: выдача задания, проверка has_..., сдача finish_....
  4. Фразы «взять» (give_info, action) и «сдать» (has_info, dont_has_info, precondition, give_info, action) — по образцу my_npc_dialog.
  5. Проверка на новой игре; смотрите лог на ERROR.

Сдвинуть точку старта игрока

В spawn/alife_zaton.ltx, блок actor (bar_actor): меняются position, upd:position, level_vertex_id; затем пересборка all.spawn.

Перед каждым запуском

  • Все новые файлы лежат в gamedata с правильной структурой папок.
  • Новые XML подключены в system.ltx.
  • Имя NPC одинаково во всех звеньях цепочки.
  • В XML один корневой тег, все теги закрыты.
  • Запущена новая игра; после запуска в логе проверен ERROR.

Частые ошибки

СимптомПричина
«Ничего не изменилось»старое сохранение; файл положили не туда; не подключили в system.ltx
NPC не появилсяне совпали имена; нет секции; повторное создание блокирует ..._spawned
NPC «пустой», нет диалогапустой start_dialog; нет use = true; разговор запрещён в meet
NPC стоит и не ходитнет пути в way_zaton.ltx (не пересобрали all.spawn); неверный level_vertex_id; не задан path_walk
Фраза не показываетсяне выполнено условие (has_info, precondition) или порядок <next> перебивает вариант
Кракозябры в текстесменили кодировку; у XML она в шапке, у npc_profile.xml — UTF-8 с BOM
Вылет при загрузкесинтаксическая ошибка в .script или XML; смотрите конец лога