Справочник мододела
Как создать нового персонажа, дать ему вещи, поставить на место, задать поведение, диалог и квест — и какие значения бывают у каждого параметра. Дополнение к документации мода «Бар», для тех, кто ещё не делал моды.
Ничего не найдено.
01Где смотреть полные списки
Архив мода содержит только изменённые файлы. Всё остальное лежит в gamedata.db0, db1 и т. д. — распакуйте их (например, STALKER DB Unpacker) в отдельную папку для справки.
| Что узнать | Где смотреть |
|---|---|
| Оружие, патроны, насадки | 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 |
| Логика стандартных NPC | configs\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())
end03Создание нового 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>- Диалог — в
dialogs_my.xml(раздел 08). - Логика — файл
configs\scripts\my_npc_4.ltx(раздел 10). - Появление в мире — раздел 12.
- Новая игра — сохранения хранят старое состояние мира.
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 | файл логики NPC | scripts\имя.ltx (от папки configs) |
story_id | уникальный ярлык для get_story_object | уникальное значение, не повторять |
Группировки (community)
| Значение | Кто это | Значение | Кто это |
|---|---|---|---|
stalker | одиночки | monolith | «Монолит» |
bandit | бандиты | zombied | зомбированные |
dolg | «Долг» | csky | «Чистое небо» |
freedom | «Свобода» | renegade | ренегаты |
killer | наёмники | greh | «Грех» |
army / military | военные (проверьте) | isg | ISG |
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 | {+имя} | {-имя} | %+имя% | %-имя% |
| Lua | has_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 |
meet | meet@имя или no_meet |
trade | misc\trade\имя.ltx |
on_info, on_timer | глобальные переходы из любой секции |
on_death | {условие} %эффект% при смерти |
combat_ignore, hit, wounded, danger, abuse | реакции на бой, попадание, ранение, опасность, оскорбления |
gather_items, help_wounded, corpse_detection | true / 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_death | NPC получил попадание / умер |
Внутри { }: +имя есть инфопорция, -имя нет, =функция(арг) вернула 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. Состояние = поза + оружие + анимация + движение.
Как добавить новое состояние
- В
state_mgr_animation_list.scriptскопируйте запись (my_idle) и переименуйте:into— вход,out— выход,idle— основная,rnd— случайные,prop.rnd— вероятность случайной в процентах,prop.maxidle/prop.sumidle— ограничения основной в секундах. - В
state_lib.scriptскопируйте состояние и задайте поля. - В поле
animationукажите имя из шага 1. - Используйте имя в логике NPC (
anim = моё_состояние).
bar_1_idle_0) должны существовать в наборе анимаций модели (meshes\actors\...\*.omf). Иначе NPC будет стоять в T-позе. Новые анимации требуют отдельных инструментов для .omf.| Поле | Значения |
|---|---|
weapon | none, strapped (за спиной), unstrapped (в руках, опущено), fire, sniper_fire, drop, nil |
movement | move.stand, move.walk, move.run, nil |
mental | anim.free (спокоен), anim.danger, anim.panic, nil |
bodystate | move.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_id | get_story_object('id') (nil, если не онлайн) |
| Объект по id | level.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Рецепты и чек-листы
Новый торговец
- Секция, профиль, карточка как в разделе 03; в карточке
<money min="100000" max="100000" infinitive="1"/>. - В логике
trade = misc\trade\твой_файл.ltx, в meet —use = true,trade_enable = true. - Файл торговли берите за основу из
configs\misc\trade\. - В диалог добавьте фразу, открывающую торговлю (скопируйте из диалога бармена).
Охранник, который не разговаривает
Пустой <start_dialog></start_dialog>; в логике meet = no_meet или use = false; оружие в <supplies>; поведение — remark с anim = guard.
Квест «принеси предмет»
- Инфопорции
..._givenи..._doneв info_my.xml. - Название и описание в string_table_my.xml.
- В my_quest.script: выдача задания, проверка
has_..., сдачаfinish_.... - Фразы «взять» (
give_info,action) и «сдать» (has_info,dont_has_info,precondition,give_info,action) — по образцуmy_npc_dialog. - Проверка на новой игре; смотрите лог на
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; смотрите конец лога |