Custom_data - это текстовое поле в объекте, где хранится пользовательская (кастомная) конфигурация, задающая поведение, свойства и логику игрового объекта (НПС, монстра, предмета, зоны и т.д.), может быть оформлена в all.spawn, либо в папке config в ltx файлах.
Ещё есть User data, её можно настроить, если открыть модель.object в Actor editor'e в поле Object, у нпс в ней может быть следующее:
#include "models\capture\kurtka_mask_damage_no.ltx" так настраивается прочность визуалов нпс
Открыв \config\creatures\m_stalker.ltx видим:
custom_data = stalker_custom_data.ltx
эту же строку можно установить и в конфиге актора \config\creatures\actor.ltx для того, чтобы при размещении актора на локации его секции заполнялись автоматически(dont_spawn_loot и тд).
либо для нпс, которых спавним из \config\creatures\spawn_sections.ltx
Оформление файла аналогично параметру cfg, разобранному ниже
В custom_data любого персонажа или объекта может присутствовать секция [logic], она нужна для настройки логики объекта
В секции должен присутствовать один из ключей:
active = …
Активная схема, запускающаяся первой.
cfg = …
Имя ltx-файла с настройками.
Если задано поле cfg, то в качестве настроек будет использовано содержимое указанного файла по пути \gamedata\config\
[logic]
cfg = scripts\sar_monolith.ltx
Внутри файла всё начинается с:
[logic]
active = Активная схема
Для нпс можно сделать заглушку active = nil, если нпс не нужна логика того же волкера, но обязательно нужна схема "death",
НО в гулаге так нельзя, вылет: gulag: ltx=%s, there is no path in section %s
В параметре active можно использовать кондлист для переключения на отдельные схемы:
[logic]
active = {условие для работы схемы } sleeper, walker
Получается так что сначала работает схема "walker", потом при выполнении условия из { } переходит на sleeper. В смартах это изначально не работает, поэтому ищем внизу фикс.
В конфигах смартов логика оформлена так:
[logic@комментарий]
это нужно для того чтобы хранить несколько логик для разных нпс в одном файле, в теории можно использовать и вне смартов
Все видео про схемы:
https://www.youtube.com/playlist?list=PLONawgtVvphyyuWnO5sQO_iKcBEUqNFT0
Параметры которые работают в каждой схеме, и предназначены для переключения схем\выполнения действий
on_actor_dist_le = <number>|<название_схемы> - если дистанция в метрах до игрока меньше либо равно number.
on_actor_dist_le_nvis = <number>|<название_схемы> - если дистанция в метрах до игрока меньше либо равно number без проверки на видимость.
on_actor_dist_ge = <number>|<название_схемы> - если дистанция в метрах до игрока больше number.
on_actor_dist_ge_nvis = <number>|<название_схемы> - если дистанция в метрах до игрока больше number без проверки на видимость.
on_signal = <имя_сигнала>|<название_схемы> - срабатывает по приходу сигнала имя_сигнала от текущей активной схемы.
on_info = <название_схемы> - срабатывает всегда.
on_timer = <number>|<название_схемы> - срабатывает через number миллисекунд после включения схемы.
on_game_timer = <number>|<название_схемы> – срабатывает через number секунд игрового времени, после включения схемы.
on_actor_in_zone = <имя_зоны>|<название_схемы> – если актер в находится в указанной зоне (указывается имя рестриктора).
on_actor_not_in_zone = <имя_зоны>|<название_схемы> – если актер не в указанной зоне (указывается имя рестриктора).
on_npc_in_zone = <number>|<имя_зоны>|<название_схемы> – если NPC со story_id равному number, в указанной зоне.
on_npc_not_in_zone = <number>|<имя_зоны>|<название_схемы> - если NPC со story_id равному number, не в указанной зоне.
on_actor_inside = <название_схемы> - зона проверяет, находится ли игрок внутри нее (используется со space_restrictor).
on_actor_outside = <название_схемы> - зона проверяет, находится ли игрок за ее пределами (используется со space_restrictor).
Все параметры поддерживают кондлист:
on_info = {+info –info =func !func ~number} walker
С любыми из вышеперечисленных параметров можно работать следующим образом:
on_info = {…} %…%
on_info2 = {…} %…%
on_info3 = {…} %…%
И так далее.
в %…% происходит выполнение
в {…} происходит проверка
везде где нужно перечесление and "И" используем по одному {…} %…% и все условия задаем в них
Как только будут выполнено условие из {…}, будет выполнено действие из %…% и, например, переключение на того же walker
Все строки должны иметь разные имена, т.е. запись:
on_info2 = …
on_info2 = …
работать не будет, сработает только второй переход.
Запись такого вида:
on_info = {…} {…} %…% %…%
недопустима
Для организации оператора or "Или" используется запятая
on_info = {условие1} %…%, {условие2} %…%
---------------------------------------------------------------------------
Условия в {…} могут быть следующими:
+infoportion - требуется присутствие инфопорции "infoportion" у актора
-infoportion - требуется отсутствие инфопорции "infoportion" у актора
=function - требуется, чтобы функция "function" вернула true
!function - требуется, чтобы функция "function" вернулся false
~number - вероятность выполнения условия.
Для вызова действий в %…% ситуация такая:
+infoportion - выдача инфопорции "infoportion" для актора
-infoportion - удаление инфопорции "infoportion" у актора
=function - вызов функции "function"
---------------------------------------------------------------------------
Функции проверки для {…} должны лежать в \gamedata\scripts\xr_conditions.script
---------------------------------------------------------------------------
Функции выполнения для %…% должны лежать в \gamedata\scripts\xr_effects.script
Очень важно как-либо прекращать выполнение функции, и пример с низу НЕПРАВИЛЬНЫЙ, функция будет вызваться бесконечно.
on_info = {+info} %=test%
ПРАВИЛЬНО:
если логика на этом моменте заканчивается, то можно сделать:
on_info = {+info} %=test% nil
либо переход на другую схему:
on_info = {+info} %=test% walker
В логических условиях принимаются ключевые слова always и never, которые означают, что условие будет выполняться всегда или никогда.
Например:
combat_ignore_cond = {=actor_enemy =actor_has_suit} always, {=actor_enemy} never %=gar_dm_bandits_fight%
Вышеприведенная конструкция включает игнорирование боя, если у NPC враг игрок в костюме, но отключит его, если врагом является игрок, но без костюма, при этом сработают эффекты (% %) секции never. Таким образом, выбор секции never равносилен отсутствию секции (несрабатыванию условия), но эффекты в знаках процента при этом срабатывают.
Чтобы разобраться получше давай рассмотрим такую custom_data:
[smart_terrains]
none = true ;none отвечает, за то чтобы никакой смарт не притягивал нашего нпс, вместо none, обычно стоит имя смарта
[logic]
active = walker ;активная схема нпс walker
on_death = death ;ссылка на схему death, она определяет что будет, когда нпс погибнет
[walker] ;схема волкера, в ней нпс стоит на месте, либо ходит по точкам
path_walk = wolf_walk ;обязательная точка пути\маршрут
path_look = wolf_look ;опциональный параметр, точка в которую смотрит нпс
meet = meet ;ссылка на схему meet
on_actor_dist_le = 23| {+tutorial_wounded_start -esc_kill_bandits_quest_kill} remark
;когда мы подойдем к нпс на растояние 23 метра и меньше и будет получен инфо "tutorial_wounded_start" и пока будет отсутствовать инфо "esc_kill_bandits_quest_kill", тогда запускается схема ремарк remark
on_actor_dist_le_nvis = 5| walker2 ;дополнительное условие переключние на walker2, если мы подошли слишком близко
danger = danger_ignore ;ссылка на схему danger
[remark] ;ремарк, схема в которой нпс отыгрывает анимации и произносит фразы, почти все параметры опциональные
anim = hello_wpn
snd = esc_wolf_say_thanks
target = actor
on_signal = sound_end| walker2 ;on_signal это проверка на всяческие события заготовленные скриптами, тут это окончание звука
on_info = {+esc_kill_bandits_quest_have} remark2 ;переключение на remark2 произойдет когда будет получен инфо "esc_kill_bandits_quest_have"
on_signal2 = sound_end| remark3 %=play_snd_from_radio% ;%=play_snd_from_radio% это запуск функции play_snd_from_radio из файла \gamedata\scripts\xr_effects.script
on_info2 = {=gulag_population_le(esc_lager:6)} walker ;{=gulag_population_le(esc_lager:6)} это функция проверки gulag_population_le из файла \gamedata\scripts\xr_conditions.script
;ниже видно эти уникальные настройки
[danger_ignore]
ignore_distance = 0
[meet]
use = true
use_wpn = true
meet_talk_enabled = true
meet_state = ward
Настройки, которые меняют поведение общих схем, в зависимости от активной в данный момент обычной схемы (все они необязательны).
"combat_ignore_cond"
combat_ignore_cond = {+info –info =func !func ~number} – условия для игнорирования боя.
"combat_ignore_keep_when_attacked"
combat_ignore_keep_when_attacked = true/false - NPC продолжает игнорировать бой, даже если в него стреляют – только в случае стрельбы игрока!!!
"combat_type"
combat_type = {+info –info =func !func ~number} <параметр> - тип боя которым будет пользоваться NPC из данной схемы. Возможны следующие значения:
camper
monolith
zombied
nil
"on_combat"
on_combat = {+info –info =func !func ~number} %+info -info =func% - определяет поведение NPC при его уходе в бой.
"companion_enabled"
companion_enabled = true/false - cвободноходящие сталкеры могут наниматься как компаньоны. Оверрайд существует, но не используется из-за недоработанной схемы xr_attendant.script.
"heli_hunter" Хелихантер может стрелять либо не стрелять по вертолету в зависимости от условий. Делается это так:
В активную схему вставляется параметр:
heli_hunter = {+info -info =func !func ~number}true/false
Помимо оверрайдов есть схемы, которые работают на постоянке, и выполняют заложенный в скриптах ряд действий, если указаны то предполагают уникальную настройку, некоторые могут ставиться к определенной схеме, а также прямо в [logic]
"death" Схема показывает, что происходит при смерти NPC
"hit" Схема показывает, что происходит при нанесении повреждения NPC
"actor_dialogs" Показывает, какие диалоги будут доступны или недоступны игроку при разговоре с этим NPC. Пишется практически в любой схеме
"combat_ignore" Если для NPC установлена данная секция то он, не переходит в боевой режим. Для секции нет дополнительных полей
"danger" Секцию необходимо указывать только для какой-то конкретной схемы!
"meet" Схема позволяющая настроить ситуацию, когда НПС встречает актора
"combat" Показывает, что происходит, когда NPC срывается в бой
"use" Схема показывает, что произойдет, если игрок попытается взаимодействовать с NPC
Помимо секции [logic] есть ещё несколько, которые работают отдельно, то есть из можно казывать выше или ниже:
"game_info" "Обучает" NPC рассказывать у костра истории и легенды
"known_info" Выдает инфо при обыске нпс
"dont_spawn_character_supplies" Если прописать эту секцию в кастом дату персонажу, то у него внутри не заспавниться стандартный набор барахла, прописанный в его профиле
"dont_spawn_loot" Всякого рода сюжетные персонажи, которые должны быть пустыми после смерти (например раненные или пленные)
"spawn" Параметр, спавнящий указанные предметы
"spawner" секция, которая присутствует как у NPC, так и у монстров, спавнит их по определенному условию (выводит в онлайн).
"smart_terrains" секция принадлежности к смарту
[smart_terrains]
имя_смарта = true
если вместо имя_смарта, указать "none",то никакой смарт не сможет захватить нашего нпс
[smart_terrains]
none = true
Ещё есть параметр "never", условие при котором нпс перестает принадлежать к смарту
[smart_terrains]
gar_angar = {+agroprom_military_case_done} never, {+gar_seryi_change_st} never, true
Также можно указывать несколько смартов и условия пребывания
[smart_terrains]
esc_lager = {-agroprom_military_case_have -esc_fanat_spawn}
mil_lager = {+agroprom_military_case_have}, {+esc_fanat_spawn}
"collide" делает объект, у которого указана кость, неподвижным
[collide]
ignore_static
ignore_ragdoll
"drop_box" отвечает за то, что выпадет из ящика
[drop_box]
community = big_box_generic
указывается точка в которую будет перемещен актор при отказе
[pt_move_if_reject]
path = esc_way_if_reject
Также хочу отметить, что у респавнов и смартов секция [logic] не нужна, в них идет сразу:
[respawn]
в респавн можно установить conditions, это условие до или после которых перестанет работать, или наоборот станет
conditions = {+esc_kill_bandits_quest_kill} 80, 0
[smart_terrain]
в смарт можно установить cond, это условие до или после которых смарт перестанет работать, или наоборот станет
cond = {+yan_kill_brain_done}
https://drive.google.com/drive/folders/1hK9qCJqspBuDSFFU8sMbUY1vWv7kY4cd?usp=drive_link
[logic]
active = nil или схема
trade = misc\файл_торговли.ltx
[trader]
buy_condition = my_generic_buy
buy это цены закупки от 100 до 30 процентов например
sell_condition = my_start_sell
цены продажи от 100 до 300 процентов например
buy_supplies = supplies_start
саплайс старт это ассортимент продажи, сколько и с какой вероятностью
Далее хотел бы отметить что в смартах логика нпс всегда сбрасывается и начинается сначала, для этого в смартах сделаны состояния, ими могут быть не только разделение на день и ночь, чтобы избежать сброса можно сделать так:
[logic@esc_lager_shustryi]
active = {+tutorial_wounded_give_info} sleeper4@esc_lager, camper@esc_lager_prisoner
Получается так что сначала работает camper(его состояние пленного), потом при выполнении условия из { }, то есть получения инфопоршн "tutorial_wounded_give_info" переходит на sleeper
Но сходу это работать не будет, сперва нужно в \gamedata\scripts\xr_gulag.script
234 строка
заменяем это
local active_section = self.ltx:r_string( section, "active" )
на это
local active_section_cond = xr_logic.cfg_get_condlist(self.ltx, section, "active", self)
local active_section = xr_logic.pick_section_from_condlist(db.actor, self, active_section_cond.condlist)
Как альтернатива:
меняем
local active_section = self.ltx:r_string( section, "active" )
на это
local active_section = xr_logic.determine_section_to_activate(self, self.ltx, section, db.actor)
тоже работает, нюансов не заметил
После этого оформляем логику так:
[logic]
active = {условие для работы схемы } sleeper, walker
Получается так что сначала работает схема "walker", потом при выполнении условия из { } переходит на sleeper
формально логика будет читаться с самого начала, но уже хотябы с заданного момента
Изначально нельзя вызывать функции не из "xr_effects.script", то есть: "%=имя_файла.название_функции%", НО если внести некоторые правки в "xr_logic.script", то станет можно.
Для этого нужно открыть \gamedata\scripts\xr_logic.script
и где-то с 578-й строки вместо этого:
if infop_conditions_met then
-- Условия выполнены. Независимо от того, задана ли секция, нужно проставить требуемые
-- infoportions:
for inum, infop in pairs(cond.infop_set) do
if db.actor == nil then
abort("TRYING TO SET INFOS THEN ACTOR IS NIL")
end
if infop.func then
if not xr_effects[infop.func] then
abort("object '%s': pick_section_from_condlist: function '%s' is " ..
"not defined in xr_effects.script", if_then_else(npc, npc:name(), "nil"), infop.func)
end
if infop.params then
xr_effects[infop.func](actor, npc, infop.params)
else
xr_effects[infop.func](actor, npc)
end
elseif infop.required then
if not has_alife_info(infop.name) then
actor:give_info_portion(infop.name)
end
else
if has_alife_info(infop.name) then
actor:disable_info_portion(infop.name)
end
end
end
if cond.section == "never" then
return nil
else
return cond.section
end
end
end
Надо написать вот это:
if infop_conditions_met then
-- Условия выполнены. Независимо от того, задана ли секция, нужно проставить требуемые
-- infoportions:
for inum, infop in pairs(cond.infop_set) do
if db.actor == nil then
abort("TRYING TO SET INFOS THEN ACTOR IS NIL")
end
if infop.func then
local func=nil
local module,fname=string.match(infop.func,"(.+)[.](.+)")
if not fname then
func=xr_effects[infop.func]
elseif _G[module] and _G[module][fname] then
func=_G[module][fname]
end
if not func then
abort("object '%s': pick_section_from_condlist: function '%s' is " ..
"not defined in xr_effects.script", iif(npc, npc:name(), "nil"), infop.func)
end
if infop.params then
func(db.actor, npc, infop.params)
else
func(db.actor, npc)
end
elseif infop.required then
if not has_alife_info(infop.name) then
actor:give_info_portion(infop.name)
end
else
if has_alife_info(infop.name) then
actor:disable_info_portion(infop.name)
end
end
end
if cond.section == "never" then
return nil
else
return cond.section
end
end
end
Есть видео где разбор начинается непосредственно с логики нпс https://vkvideo.ru/video-226619903_456239347, но давайте копнем ещё глубже.
Если у НПС указана секция [smart_terrains], то он автоматически попадает под действие скрипта xr_gulag.script, в нем:
в функции gulag:setup_logic( obj, section )
происходит запуск функций, которые будет определять работу нпс:
xr_logic.configure_schemes()
xr_logic.activate_by_section()
ниже будет их разбор.
В то же время у каждого НПС есть биндер, он нужен для того чтобы собирать информацию и определённым образом реагировать на некоторые действия. Если у нпс не указан смарт или стоит none, то нпс будет бродить в алайфе, либо исполнять заданную логику в кастом дате.
Если биндер отключить и нпс будет не под смартом, то нпс не сможет исполнять логику он перейдет в алайф, в то же время гулаги работать будут, но из-за отсутствия информации из биндера, нпс встанут на свои места и не смогут исполнять свои обязаности.
Биндер подключен в m_stalker.ltx
script_binding = bind_stalker.init
Это значит, что для каждого нпс в игре будет осуществлен запуск функции init из bind_stalker.script
function init (obj)
xr_motivator.AddToMotivator(obj)
end
AddToMotivator это функция из xr_motivator.script
function AddToMotivator(npc)
if alife() then
npc:bind_object(this.motivator_binder(npc))
end
end
bind_object движковый метод
motivator_binder это псевдо-класс на основе движкового класса object_binder
На стадии net_spawn идет вызов xr_gulag
xr_gulag.setup_gulag_and_logic_on_spawn( self.object, self.st, sobject, modules.stype_stalker, self.loaded )
а в нем идет вызов xr_logic
xr_logic.initialize_obj(obj, st, loaded, db.actor, stype)
в этой функции идет запуск двух интересных функций:
Первая xr_logic.configure_schemes
она интересна запуском функций из xr_logic.script
disable_generic_schemes(npc, stype)
и
enable_generic_schemes(actual_ini, npc, stype, section_logic)
Через enable_generic_schemes, для понимания, через неё у нас идет запуск компаньона, а так же активация схем: "danger", "on_combat", "meet", "on_death"
таким макаром
local meet_section = utils.cfg_get_string (ini, section, "meet", npc, false, "")
xr_meet.set_meet (npc, ini, "meet", meet_section)
то есть в логике ищется именно параметр meet условно в [walker], вот как у Волка:
[walker@esc_lager_volk_walker1]
path_walk = wolf_walk
meet = meet@wolf
[meet@wolf]
meet_state = 15|guard@wait
Но xr_meet всё равно работает, просто при нахождении секции он подставит параметры.
Вторая xr_logic.activate_by_section
она в свою очередь делает заветный запуск всех схем по типу "walker", "camper", "remark", и наш "karavan", когда находит их активными
_G[filename].set_scheme(npc, ini, scheme, section, db.storage[npc_id]["gulag_name"])
filename определяется за счёт modules.script
в котором указаны все схемы:
load_scheme("xr_walker", "walker", stype_stalker)
load_scheme это функция из _g.script, там происходит заполнение таблицы, благодаря которой скрипту будет понятно из какого файла запускать функцию set_scheme
то есть когда у нпс такая логика
[logic]
active = walker
[walker]
path_walk = my_way_walk1
функция activate_by_section подставляет xr_walker, вместо _G[filename] и запускает функцию set_scheme из xr_walker.script
она в свою очередь запускает
xr_logic.assign_storage_and_bind
которая делает вызов
_G[schemes[scheme]].add_to_binder(npc, ini, scheme, section, st)
и опять же обращается к modules.script, поэтому в него должны быть внесены все схемы.
Если брать в пример xr_meet, то там функция set_scheme названа set_meet, и это не критично поскольку вызывали мы её из enable_generic_schemes, который был вызван в configure_schemes
так вот эта строка делает запуск функции add_to_binder из xr_walker.script а она уже является планировщиком.
Далее пойдет копипаст статьи Red75 (https://web.archive.org/web/20250725115402/https://sdk.stalker-game.com/ru/index.php?title=%D0%9B%D0%BE%D0%B3%D0%B8%D0%BA%D0%B0_NPC), пытаюсь объяснить работу функции add_to_binder
Функция получает планировщик для текущего объекта (object).
local manager = npc:motivation_action_manager()
Планировщик будет периодически проверять условие (вызывать метод evaluate() эвалуатора), и если оно выполняется, инициализирует и будет выполнять оператор, пока условие не станет ложным.
Затем присваивает идентификаторы, которые могут иметь любое целочисленное значение, главное, чтобы они были уникальными.
properties["need_walker"] = xr_evaluators_id.zmey_walker_base + 1
operators["action_walker"] = xr_actions_id.zmey_walker_base + 1
Далее создаётся эвалуатор и добавляется в планировщик.
manager:add_evaluator(properties["need_walker"], this.evaluator_need_walker(storage, "walker_need"))
Теперь создаётся оператор
local new_action = this.action_walker_activity(npc, "action_walker_activity", storage)
Задаются предусловия для этого оператора. Планировщик выберет этот оператор при выполнении всех условий. Всё это значит примерно следующее: я могу идти к точке, если:
я живой,
new_action:add_precondition(world_property(stalker_ids.property_alive, true))
опасностей нет,
new_action:add_precondition(world_property(stalker_ids.property_danger,false))
врагов нет,
new_action:add_precondition(world_property(stalker_ids.property_enemy, false))
аномалий поблизости нет,
new_action:add_precondition(world_property(stalker_ids.property_anomaly,false))
выполняются другие важные условия (игрок не собирается со мной поговорить, я не собираюсь никого бить по морде, я не ранен, я не собираюсь стрелять по вертолёту),
xr_motivator.addCommonPrecondition(new_action)
еволютор дал тру, и можно идти к цели
new_action:add_precondition(world_property(properties["need_walker"], true))
Выполнили действие, закончили(и так по кругу)
new_action:add_effect(world_property(properties["need_walker"], false))
Создание оператора завершено. Добавим его в планировщик.
manager:add_action(operators["action_walker"], new_action)
Эта строчка не имеет отношения к работе планировщика. Если коротко, то она позволяет объекту получать уведомления об определённых событиях (смерть - вызывается метод death_callback(), попадание пули - вызывается метод hit_callback() и т.д.)
xr_logic.subscribe_action_for_events(npc, storage, new_action)
Осталось ещё одна задача. Нужно запретить планировщику активировать оператор «alife», тот самый оператор, который заставляет NPC болтаться по карте, отстреливать собачек и в конце концов попадать в аномалию. Впрочем, отстрелом врагов занимается другой оператор с идентификатором stalker_ids.action_combat_planner.
Для этого мы получаем оператор «alife»
new_action = manager:action(xr_actions_id.alife)
И добавляем к его предусловиям следующее: условие «не пришёл ли я?» должно быть истинным.
new_action:add_precondition(world_property(properties["need_walker"], false))
Итак, мы настроили планировщик. Посмотрим как всё это будет работать.
Эволютор волкера требует одного условия, которое должно дать true
function evaluator_need_walker:evaluate()
return xr_logic.is_active(self.object, self.st)
это чтобы текущая активная схема нпс была [walker] дальше будет работать экшен тут я немного поплыл, ибо методы немного странные, но в волкере ещё на этапе set_scheme и дет чтение параметров
st.path_walk = utils.cfg_get_string(ini, section, "path_walk", npc, true, gulag_name)
st.path_look = utils.cfg_get_string(ini, section, "path_look", npc, false, gulag_name)
из чего видим что path_walk обязательный, а path_look нет.
Как имено он идет я не разобрался, но судя по всему из-за этого
self.move_mgr:reset(self.st.path_walk, self.st.path_walk_info, self.st.path_look, self.st.path_look_info, self.st.team, self.st.suggested_state)
Возвращаемся к xr_motivator.script вспоминаем, что там работает некий биндер нпс - motivator_binder, за счёт которого пашут все схемы, поведение нпс, если по простому.
так вот на стадии
function motivator_binder:reinit()
можно заметить запуск двух других биндеров нпс
self.st.state_mgr = state_mgr.bind_manager(self.object)
self.st.move_mgr = move_mgr.move_mgr(self.object)
Вот благодаря этому, данные полученные со схемы волкер начинают иметь смысл.
Данные, попав в биндер стейт/мув менеджера, определяют для нпс куда ему и как идти.
Делаю вывод, что поведение нпс в алайфе предопределено в движке, и все эти скрипты нужны для работы схем, этому вроде ничего не противоречит, должен же быть фундамент
state_mgr.script - это реализация менеджера состояний тела НПС в S.T.A.L.K.E.R., разработанная Русланом Диденко (Stohe). Он отвечает за управление анимацией, позой, направлением взгляда и оружием НПС на уровне онлайн-поведения, то есть когда НПС находится на той же локации, что и игрок.
🔍 Основная цель state_mgr.script
Этот скрипт не управляет глобальной логикой (это делают смарт-террейны и гулаги), а реализует «физическое» поведение тела персонажа:
стоит, сидит, крадётся, бежит;
смотрит в определённую точку или на объект;
держит оружие наизготовку, стреляет, убирает;
синхронизирует анимацию и звук.
Всё это происходит в рамках активной схемы (walker, camper, remark и т.д.), но state_mgr предоставляет низкоуровневый интерфейс для задания состояний тела через систему GOAP-планировщика (Goal-Oriented Action Planning).
🧠 Ключевые компоненты
1. state_manager - основной класс
Хранит текущее желаемое состояние: target_state (например, "idle", "hide", "assault").
Управляет направлением взгляда (look_position, look_object).
Поддерживает флаги: combat, alife - для переключения между режимами.
2. Эвалюаторы (evaluators) - условия для перехода
evaluator_state_mgr_idle_alife - проверяет, находится ли НПС в ALife-режиме.
Устанавливает self.st.alife = true, если:
у НПС нет активной схемы (active_section == nil);
НПС не в состоянии meet (xr_meet.is_meet() == false);
текущее состояние тела - idle.
evaluator_state_mgr_idle - аналогичен, но для боевого/сценарного режима (combat = true).
Эти эвалюаторы интегрированы в motivation_action_manager и определяют, когда НПС «выходит» из ALife в сценарное поведение и обратно.
3. Действия (actions) - реакция на условия
act_state_mgr_to_idle - переводит НПС в состояние idle.
act_state_mgr_end - главный «рабочий» экшен, который:
управляет стрельбой (в т.ч. снайперской);
обновляет состояние оружия (fire, idle, unstrapped);
учитывает, видит ли НПС цель (best_enemy, see()).