e4s-sdk/gamedata/scripts/smart_terrain.script
2026-06-17 23:06:51 +03:00

1427 lines
52 KiB
Text
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

local DEATH_IDLE_TIME = 10*60 -- секунд
SMART_TERRAIN_SECT = "smart_terrain"
smart_terrains_by_name = {}
local locations_ini = ini_file("misc\\smart_terrain_masks.ltx")
nearest_to_actor_smart = {id = nil , dist = math.huge}
local path_fields = { "path_walk", "path_main", "path_home", "center_point" }
local valid_territory = {
default = true,
base = true,
resource = true,
territory = true
}
--' Проверка, что нпс подходит работе
local function job_avail_to_npc(npc_info, job_info, smart)
--printf("job_avail_to_npc %s job %s smart %s", npc_info.se_obj:name(), tostring(job_info.job_id), smart:name())
local job = smart.job_data[job_info.job_id]
if job ~= nil then
job = job.section
end
if smart.dead_time[job_info.job_id] ~= nil then
return false
end
-- Проверка условия "монстровости"
if job_info._precondition_is_monster ~= nil and job_info._precondition_is_monster ~= npc_info.is_monster then
return false
end
--' Проверяем подходит ли нпс по предикату
if job_info._precondition_function ~= nil then
if not job_info._precondition_function(npc_info.se_obj, smart, job_info._precondition_params, npc_info) then
return false
end
end
return true
end
-- Итерируемся по НПС, начинаем со свободных нпс, потом НПС на низкоприоритетных работах, потом на высокоприоритетных.
-- Для каждого конкретного НПС ищем работу.
-- Отсеиваем в поиске работы, приоритет которых ниже, чем у текущей.
local function job_iterator(jobs, npc_data, selected_job_prior, smart)
--printf(" iterate")
-- итерируемся по работам
local current_job_prior = selected_job_prior
local selected_job_id = nil
local selected_job_link = nil
for k,v in pairs(jobs) do
-- Если приоритет у проверяемой работы ниже, чем приоритет текущей выбранной работы НПС - завершаем выполнение
if current_job_prior > v._prior then
return selected_job_id, current_job_prior, selected_job_link
end
-- Проверяем, может ли НПС занять данную работу
if job_avail_to_npc(npc_data, v, smart) then
-- Это работа-кластер или работа-описание.
if v.job_id == nil then
-- Вызываем рекурсивно себя для списка работ кластера
selected_job_id, current_job_prior, selected_job_link = job_iterator(v.jobs, npc_data, selected_job_prior, smart)
else
-- Если работа пустая или ее занимаем мы сами - выбираем ее.
if v.npc_id == nil then
return v.job_id, v._prior, v
elseif v.job_id == npc_data.job_id then
return v.job_id, v._prior, v
end
end
end
end
return selected_job_id, current_job_prior, selected_job_link
end
-- Расстояние до работы
local function arrived_to_smart(obj, smart)
local obj_gv, obj_pos
local storage = db.storage[obj.id]
if storage == nil then
obj_gv, obj_pos = game_graph():vertex(obj.m_game_vertex_id), obj.position
else
local obj = db.storage[obj.id].object
obj_gv, obj_pos = game_graph():vertex(obj:game_vertex_id()), obj:position()
end
local smart_gv = game_graph():vertex(smart.m_game_vertex_id)
if obj.group_id then
local squad = smart.board.squads[obj.group_id]
if squad ~= nil and squad.current_action then
if squad.current_action.name == "reach_target" then
local squad_target = simulation_objects.get_sim_obj_registry().objects[squad.assigned_target_id]
if squad_target ~= nil then
return squad_target:am_i_reached(squad)
else
return alife():object(squad.assigned_target_id):am_i_reached(squad)
end
elseif squad.current_action.name == "stay_point" then
return true
end
end
end
if obj_gv:level_id() == smart_gv:level_id() then
return obj_pos:distance_to_sqr(smart.position) <= 10000 --Ближе 100 метров
else
return false
end
end
----------------------------------------------------------------------------------------------------------------------
-- Класс "se_smart_terrain". Обеспечивает поддержку smart terrain в ОФЛАЙНЕ.
----------------------------------------------------------------------------------------------------------------------
class "se_smart_terrain" (cse_alife_smart_zone)
function se_smart_terrain:__init(section) super(section)
self.initialized = false
self.b_registred = false
self.population = 0
self.npc_to_register = {}
self.npc_by_job_section = {}
self.dead_time = {}
-- Таблица для хранения зарегистренных НПС
self.npc_info = {} -- Те, кто уже пришел и стал на работу
self.arriving_npc = {} -- Только идущие на работу.
self.respawn_radius = 150
self.respawn_time = 1000
end
function se_smart_terrain:on_before_register()
cse_alife_smart_zone.on_before_register(self)
self.board = sim_board.get_sim_board()
self.board:register_smart(self)
self.smart_level = alife():level_name(game_graph():vertex(self.m_game_vertex_id):level_id())
--printf("SMARTLEVEL %s level %s", self:name(), tostring(self.smart_level))
end
function se_smart_terrain:on_register()
cse_alife_smart_zone.on_register(self)
-- Проверяем кастомдату обьекта на наличие стори айди.
story_objects.check_spawn_ini_for_story_id(self)
simulation_objects.get_sim_obj_registry():register(self)
printf("register smart %s", self:name())
if dev_dedug then
self:refresh()
end
printf("Returning alife task for object [%s] game_vertex [%s] level_vertex [%s] position %s", self.id, self.m_game_vertex_id, self.m_level_vertex_id, vec_to_str(self.position))
self.smart_alife_task = CALifeSmartTerrainTask(self.m_game_vertex_id, self.m_level_vertex_id)
smart_terrains_by_name[self:name()] = self
self.b_registred = true
self:load_jobs()
self.board:init_smart(self)
if self.need_init_npc == true then
self.need_init_npc = false
self:init_npc_after_load()
end
-- Регистрим персонажей, которые добавили до регистрации смарта. (отложенный список)
self:register_delayed_npc()
self.check_time = time_global()
end
-- анрегистрация объекта в симуляторе.
-- вызывается симулятором.
function se_smart_terrain:on_unregister()
cse_alife_smart_zone.on_unregister(self)
self.board:unregister_smart(self)
smart_terrains_by_name[self:name()] = nil
unregister_story_object_by_id(self.id)
simulation_objects.get_sim_obj_registry():unregister(self)
end
-- чтение custom data.
function se_smart_terrain:read_params()
self.ini = self:spawn_ini()
if not self.ini:section_exist( SMART_TERRAIN_SECT ) then
abort( "[smart_terrain %s] no configuration!", self:name() )
self.disabled = true
return
end
local filename = utils.cfg_get_string(self.ini, SMART_TERRAIN_SECT, "cfg", self, false, "")
local fs = getFS()
if filename and filename~="" then
if fs:exist("$game_config$",filename) then
self.ini = ini_file(filename)
else
abort("There is no configuration file [%s] in smart_terrain [%s]", filename, self:name())
end
end
local ini = self.ini
self.sim_type = utils.cfg_get_string(ini, SMART_TERRAIN_SECT, "sim_type", self, false, "", "default")
--' Вычитка симуляционных свойств
if valid_territory[self.sim_type] == nil then
abort("Wrong sim_type value [%s] in smart [%s]", self.sim_type, self:name())
end
self.squad_id = utils.cfg_get_number(ini, SMART_TERRAIN_SECT, "squad_id", self, false, 0)
self.respawn_sector = utils.cfg_get_string(ini, SMART_TERRAIN_SECT, "respawn_sector", self, false, "")
self.respawn_radius = utils.cfg_get_number(ini, SMART_TERRAIN_SECT, "respawn_radius", self, false, 150) -- радиус респауна (если актер ближе, то не спаунить)
self.respawn_time = utils.cfg_get_number(ini, SMART_TERRAIN_SECT, "respawn_time", self, false, 1000) -- в секундах игрового времени
if self.respawn_sector ~= nil then
if self.respawn_sector == "default" then
self.respawn_sector = "all"
end
self.respawn_sector = xr_logic.parse_condlist(nil, SMART_TERRAIN_SECT, "respawn_sector", self.respawn_sector)
end
self.mutant_lair = utils.cfg_get_bool(ini, SMART_TERRAIN_SECT, "mutant_lair", self, false)
self.no_mutant = utils.cfg_get_bool(ini, SMART_TERRAIN_SECT, "no_mutant", self, false)
if self.no_mutant == true then
printf("Found no mutant point %s", self:name())
end
self.forbidden_point = utils.cfg_get_string(ini, SMART_TERRAIN_SECT, "forbidden_point", self, false, "")
--' Рестрикторы для симуляции
self.def_restr = utils.cfg_get_string(ini, SMART_TERRAIN_SECT, "def_restr", self, false, "", nil)
self.att_restr = utils.cfg_get_string(ini, SMART_TERRAIN_SECT, "att_restr", self, false, "", nil)
self.safe_restr = utils.cfg_get_string(ini, SMART_TERRAIN_SECT, "safe_restr", self, false, "", nil)
self.spawn_point = utils.cfg_get_string(ini, SMART_TERRAIN_SECT, "spawn_point", self, false, "")
self.arrive_dist = utils.cfg_get_number(ini, SMART_TERRAIN_SECT, "arrive_dist", self, false, 30)
-- self.max_population = utils.cfg_get_number(ini, SMART_TERRAIN_SECT, "max_population", self, false, 0)
local max_population = utils.cfg_get_string(ini, SMART_TERRAIN_SECT, "max_population", self, false, "", 0)
local parsed_condlist = xr_logic.parse_condlist(nil, SMART_TERRAIN_SECT, "max_population", max_population)
self.max_population = tonumber(xr_logic.pick_section_from_condlist(get_story_object("actor"), nil, parsed_condlist))
-- self.sim_avail = utils.cfg_get_string(ini, SMART_TERRAIN_SECT, "sim_avail", self, false, "")
-- if self.sim_avail ~= nil then
-- self.sim_avail = xr_logic.parse_condlist(nil, SMART_TERRAIN_SECT, "sim_avail", self.sim_avail)
-- end
local respawn_params = utils.cfg_get_string(ini, SMART_TERRAIN_SECT, "respawn_params", self, false, "", nil)
self.respawn_only_smart = utils.cfg_get_bool(ini, SMART_TERRAIN_SECT, "respawn_only_smart", self, false, false)
local smart_control_section = utils.cfg_get_string(ini, SMART_TERRAIN_SECT, "smart_control", self, false, "", nil)
if smart_control_section ~= nil then
self.base_on_actor_control = smart_terrain_control.CBaseOnActorControl(self, ini, smart_control_section)
end
self.respawn_point = false
if respawn_params ~= nil then
self:check_respawn_params(respawn_params)
end
if level.patrol_path_exists(self:name() .. "_traveller_actor") then
printf("Smart_terrain [%s] has no traveller_actor path!!!!!", self:name())
self.traveler_actor_path = self:name() .. "_traveller_actor"
end
if level.patrol_path_exists(self:name() .. "_traveller_squad") then
printf("Smart_terrain [%s] has no traveller_squad path!!!!!", self:name())
self.traveler_squad_path = self:name() .. "_traveller_squad"
end
if not locations_ini:section_exist(self:name()) then
printf("! SMART_TERRAIN [%s] has no terrain_mask section in smart_terrain_masks.ltx!!!",self:name())
end
end
--*******************************************************
-- МЕТОДЫ ДЛЯ РАБОТЫ С НПС
--*******************************************************
-- заполнить информацию о персонаже
-- у монстров нету метода profile_name()
function se_smart_terrain:fill_npc_info(obj)
local npc_info = {}
printf("filling npc_info for obj [%s]", tostring(obj:name()))
local is_stalker = IsStalker(obj)
npc_info.se_obj = obj
npc_info.is_monster = not is_stalker
npc_info.need_job = "nil" -- Специально для смены гвардов. Указывает на какую работу хочет данный чувак.
npc_info.job_prior = -1
npc_info.job_id = -1
npc_info.begin_job = false
if is_stalker then
npc_info.stype = modules.stype_stalker
else
npc_info.stype = modules.stype_mobile
end
return npc_info
end
function se_smart_terrain:refresh_script_logic(obj_id)
local object = alife():object(obj_id)
local stype = modules.stype_mobile
if IsStalker(object) then
stype = modules.stype_stalker
end
xr_logic.initialize_obj(db.storage[object.id].object, db.storage[object.id], false, db.actor, stype)
end
-- добавить npc в smart terrain.
function se_smart_terrain:register_npc(obj)
printf("[smart_terrain %s] register called obj=%s", self:name(), obj:name())
self.population = self.population + 1
if self.b_registred == false then
table.insert(self.npc_to_register, obj)
return
end
-- Только для монстров, чтобы ходили по смартам.
if not IsStalker(obj) then
obj:smart_terrain_task_activate()
end
obj.m_smart_terrain_id = self.id
if arrived_to_smart(obj, self) then
self.npc_info[obj.id] = self:fill_npc_info(obj)
-- Затычка на случай если мы регистримся в смарт, из которого только что сами вынесли всех врагов.
self.dead_time = {}
-- тут надо найти чуваку работу
self:select_npc_job(self.npc_info[obj.id])
else
self.arriving_npc[obj.id] = obj
end
end
-- Регистрация НПС в список отложенных. Осуществляется на загрузке или на регистрации НПС, пока не зарегистрен смарт
function se_smart_terrain:register_delayed_npc()
for k,v in pairs(self.npc_to_register) do
self:register_npc(v)
end
self.npc_to_register = {}
end
-- отпустить npc
function se_smart_terrain:unregister_npc(obj)
--callstack()
printf("smart [%s] unregister npc [%s]", self:name(), obj:name())
self.population = self.population - 1
if self.npc_info[obj.id] ~= nil then
-- TODO: Тут надо выгнать чувака с занимаемой им работы
if self.npc_info[obj.id].job_link == nil then
return
end
self.npc_info[obj.id].job_link.npc_id = nil
self.npc_info[obj.id] = nil
obj:clear_smart_terrain()
if db.storage[obj.id] ~= nil then
local object = db.storage[obj.id].object
local stype = modules.stype_mobile
if IsStalker(obj) then
stype = modules.stype_stalker
end
xr_logic.initialize_obj(object, db.storage[obj.id], false, db.actor, stype)
end
return
end
if self.arriving_npc[obj.id] ~= nil then
self.arriving_npc[obj.id] = nil
obj:clear_smart_terrain()
return
end
abort("self.npc_info[obj.id] = nil !!! obj.id=%d", obj.id)
end
-- Убрать убитого
function se_smart_terrain:clear_dead(obj)
if self.npc_info[obj.id] ~= nil then
-- Устанавливаем таймер смерти на работе
self.dead_time[self.npc_info[obj.id].job_id] = game.get_game_time()
if self.npc_info[obj.id].job_link == nil then
return
end
self.npc_info[obj.id].job_link.npc_id = nil
self.npc_info[obj.id] = nil
obj:clear_smart_terrain()
return
end
if self.arriving_npc[obj.id] ~= nil then
self.arriving_npc[obj.id] = nil
obj:clear_smart_terrain()
return
end
abort("self.npc_info[obj.id] = nil !!! obj.id=%d", obj.id)
end
-- выдать объекту задание.
function se_smart_terrain:task(obj)
if self.arriving_npc[obj.id] ~= nil then
return self.smart_alife_task
end
return self.job_data[self.npc_info[obj.id].job_id].alife_task
end
--*******************************************************
-- Функции для работы с работами
--*******************************************************
-- Загрузка работ (из gulag_general)
function se_smart_terrain:load_jobs()
--printf("LOAD JOBS %s", self:name())
-- Загружаем иерархию работ
self.jobs = gulag_general.load_job(self)
-- Загружаем ltx работ.
self.ltx, self.ltx_name = xr_gulag.loadLtx(self:name())
-- Сортируем всю иерархию по уменьшению приоритета
-- Рекурсивная функция сортировки
local function sort_jobs(jobs)
for k,v in pairs(jobs) do
if v.jobs ~= nil then
sort_jobs(v.jobs)
end
end
table.sort(jobs, function(a,b) return a._prior > b._prior end )
end
-- if self:name() == "jup_a10_smart_terrain" then
-- printf("before sort")
-- store_table(self.jobs)
-- end
sort_jobs(self.jobs)
--if self:name() == "jup_a10_smart_terrain" then
-- printf("after sort")
-- store_table(self.jobs)
--end
-- Надо сделать постобработку работ. Проинитить все неиниченные поля
-- Для более быстрого доступа нужно вычленить параметры работ в отдельную таблицу вида:
--self.job_data[job_id] = {}
local id = 0
self.job_data = {}
local function get_jobs_data(jobs)
for k,v in pairs(jobs) do
if v.jobs ~= nil then
get_jobs_data(v.jobs)
else
if v.job_id == nil then
print_table(self.jobs)
abort("Incorrect job table")
end
self.job_data[id] = v.job_id
self.job_data[id]._prior = v._prior -- Кешируем для проверки
v.job_id = id
id = id + 1
end
end
end
get_jobs_data(self.jobs)
-- Пробегаемся по работам и высчитываем для каждой работы alife_task
for k,v in pairs(self.job_data) do
local section = v.section
local ltx = v.ini_file or self.ltx
if not ltx:line_exist(section, "active") then
abort("gulag: ltx=%s no 'active' in section %s", self.ltx_name, section)
end
local active_section = ltx:r_string(section, "active")
-- printf("job_type %s job_section %s", tostring(v.job_type), tostring(section))
-- В зависимости от типа работы по разному считаем alife_path
if v.job_type == "path_job" then -- работа задается патрульным путем
local path_field
for i,vv in pairs(path_fields) do
if ltx:line_exist(active_section, vv) then
path_field = vv
break
end
end
--printf("path_field %s prefix_name %s active_section %s", tostring(path_field), tostring(v.prefix_name), tostring(active_section))
local path_name = ltx:r_string(active_section, path_field)
if v.prefix_name ~= nil then
path_name = v.prefix_name .. "_" .. path_name
else
path_name = self:name() .. "_" .. path_name
end
if path_field == "center_point" then --' TODO убрать затык когда переделаем кемпы на смарткаверы
if level.patrol_path_exists(path_name .. "_task") then
path_name = path_name .. "_task"
end
end
v.alife_task = CALifeSmartTerrainTask(path_name)
elseif v.job_type == "smartcover_job" then -- работа задается смарткавером
local smartcover_name = ltx:r_string(active_section, "cover_name")
local smartcover = se_smart_cover.registered_smartcovers[smartcover_name]
if smartcover == nil then
abort("There is an exclusive job with wrong smatrcover name [%s] smartterrain [%s]", tostring(smartcover_name), self:name())
end
printf("Returning alife task for object [%s] game_vertex [%s] level_vertex [%s] position %s", smartcover.id, smartcover.m_game_vertex_id, smartcover.m_level_vertex_id, vec_to_str(smartcover.position))
v.alife_task = CALifeSmartTerrainTask(smartcover.m_game_vertex_id, smartcover.m_level_vertex_id)
elseif v.job_type == "point_job" then -- работа задается позицией
v.alife_task = self.smart_alife_task
end
v.game_vertex_id = v.alife_task:game_vertex_id()
v.level_id = game_graph():vertex(v.game_vertex_id):level_id()
v.position = v.alife_task:position()
end
end
-- Апдейт работ смарттеррейна.
-- Если передается object, то значит нужно найти только для него
function se_smart_terrain:update_jobs()
self:check_alarm()
--printf("UPDATE JOBS %s", self:name())
-- Проверяем, дошел ли кто-то до смарта
for k,v in pairs(self.arriving_npc) do
if arrived_to_smart(v, self) then
self.npc_info[v.id] = self:fill_npc_info(v)
-- Затычка на случай если мы регистримся в смарт, из которого только что сами вынесли всех врагов.
self.dead_time = {}
-- тут надо найти чуваку работу
self:select_npc_job(self.npc_info[v.id])
self.arriving_npc[k] = nil
end
end
-- Сортируем НПС по увеличению приоритета занимаемой работы
table.sort(self.npc_info, function(a,b) return a.job_prior < b.job_prior end )
for k,v in pairs(self.npc_info) do
self:select_npc_job(v)
end
end
-- Выбор работы для персонажа
function se_smart_terrain:select_npc_job(npc_info)
-- Выбираем работу
local selected_job_id, selected_job_prior, selected_job_link = job_iterator(self.jobs, npc_info, 0, self)
if selected_job_id == nil then
print_table(self.jobs)
abort("Insufficient smart_terrain jobs %s", self:name())
end
-- Назначаем работу
if selected_job_id ~= npc_info.job_id and selected_job_link ~= nil then
-- Установить себе выбранную работу
--printf("NPC %s FOUND JOB %s SECTION %s", npc_info.se_obj:name(), selected_job_id, self.job_data[selected_job_link.job_id].section)
-- Если НПС был на работе - выгоняем его с нее.
if npc_info.job_link ~= nil then
self.npc_by_job_section[self.job_data[npc_info.job_link.job_id].section] = nil
npc_info.job_link.npc_id = nil
end
selected_job_link.npc_id = npc_info.se_obj.id
self.npc_by_job_section[self.job_data[selected_job_link.job_id].section] = selected_job_link.npc_id
npc_info.job_id = selected_job_link.job_id
npc_info.job_prior = selected_job_link._prior
npc_info.begin_job = false
-- сохраняем ссылку на работу, для облегчения удаления
npc_info.job_link = selected_job_link
-- завершаем текущую работу
local obj_storage = db.storage[npc_info.se_obj.id]
if obj_storage ~= nil then
xr_logic.switch_to_section(obj_storage.object, self.ltx, "nil")
end
end
if npc_info.begin_job ~= true then
-- Проверяем, дошел ли персонаж до работы (то есть может ли он начать ее выполнение)
local job_data = self.job_data[npc_info.job_id]
if job_data == nil then
return
end
-- Начинаем выполнять работу
printf("[smart_terrain %s] gulag: beginJob: obj=%s job= %s", self:name(), npc_info.se_obj:name(), job_data.section)
-- Смена работы, очищаем память для оффлайнового обьекта.
db.offline_objects[npc_info.se_obj.id] = {}
npc_info.begin_job = true
local obj_storage = db.storage[npc_info.se_obj.id]
if obj_storage ~= nil then
self:setup_logic(obj_storage.object)
end
end
end
-- настроить логику для объекта, который в онлайне.
function se_smart_terrain:setup_logic(obj)
--printf("setup npc logic %s", obj:name())
-- callstack()
local npc_data = self.npc_info[obj:id()]
if npc_data == nil then
return
end
local job = self.job_data[npc_data.job_id]
local ltx = job.ini_file or self.ltx
local ltx_name = job.ini_path or self.ltx_name
xr_logic.configure_schemes(obj, ltx, ltx_name, npc_data.stype, job.section, job.prefix_name or self:name())
local sect = xr_logic.determine_section_to_activate(obj, ltx, job.section, db.actor)
if utils.get_scheme_by_section(job.section) == "nil" then
abort("[smart_terrain %s] section=%s, don't use section 'nil'!", self:name(), sect)
end
xr_logic.activate_by_section(obj, ltx, sect, job.prefix_name or self:name(), false)
end
-- получить работу, которую занимает объект
function se_smart_terrain:getJob(obj_id)
return self.npc_info[obj_id] and self.job_data[self.npc_info[obj_id].job_id]
end
-- Получение персонажа, который занимает указанную работу.
function se_smart_terrain:idNPCOnJob(job_name)
return self.npc_by_job_section[job_name]
end
function se_smart_terrain:switch_to_desired_job(npc)
-- Берем текущую работу НПС
local npc_id = npc:id()
local npc_info = self.npc_info[npc_id]
--printf("***** %s -> %s", npc:name(), tostring(npc_info.need_job))
local changing_npc_id = self.npc_by_job_section[npc_info.need_job]
--printf("changing_npc_id %s", tostring(changing_npc_id))
if changing_npc_id == nil then
-- Мы не нашли с кем меняться, просто ресетим себя
self.npc_info[npc_id].job_link = nil
self.npc_info[npc_id].job_id = -1
self.npc_info[npc_id].job_prior = -1
self:select_npc_job(self.npc_info[npc_id])
--print_table(self.npc_by_job_section)
--abort("ERROR during channging NPC")
return
end
if self.npc_info[changing_npc_id] == nil then
-- Мы не нашли с кем меняться, просто ресетим себя
self.npc_info[npc_id].job_link = nil
self.npc_info[npc_id].job_id = -1
self.npc_info[npc_id].job_prior = -1
self:select_npc_job(self.npc_info[npc_id])
--print_table(self.npc_by_job_section)
--abort("ERROR during channging NPC")
return
end
local desired_job = self.npc_info[changing_npc_id].job_id
-- Переключаем НПС на желаемую работу
if npc_info.job_link ~= nil then
self.npc_by_job_section[self.job_data[npc_info.job_link.job_id].section] = nil
npc_info.job_link.npc_id = nil
end
local selected_job_link = self.npc_info[changing_npc_id].job_link
selected_job_link.npc_id = npc_info.se_obj.id
self.npc_by_job_section[self.job_data[selected_job_link.job_id].section] = selected_job_link.npc_id
npc_info.job_id = selected_job_link.job_id
npc_info.job_prior = selected_job_link._prior
npc_info.begin_job = true
-- сохраняем ссылку на работу, для облегчения удаления
npc_info.job_link = selected_job_link
npc_info.need_job = "nil"
local obj_storage = db.storage[npc_id]
if obj_storage ~= nil then
self:setup_logic(obj_storage.object)
end
-- Освобождаем НПС, который занимает желаемую работу и говорим ему перевыбрать работу
self.npc_info[changing_npc_id].job_link = nil
self.npc_info[changing_npc_id].job_id = -1
self.npc_info[changing_npc_id].job_prior = -1
self:select_npc_job(self.npc_info[changing_npc_id])
end
--*******************************************************
-- СЕЙВ/ЛОАД
--*******************************************************
-- сохранение
function se_smart_terrain:STATE_Write(packet)
cse_alife_smart_zone.STATE_Write(self, packet)
set_save_marker(packet, "save", false, "se_smart_terrain")
-- Информацию о НПС, идущих в смарт
local n = 0
for k,v in pairs(self.arriving_npc) do
n = n + 1
end
packet:w_u8(n)
for k,v in pairs(self.arriving_npc) do
packet:w_u16(k)
end
-- Информацию о НПС в смарте
n = 0
for k,v in pairs(self.npc_info) do
n = n + 1
end
packet:w_u8(n)
for k,v in pairs(self.npc_info) do
packet:w_u16(k)
packet:w_u8(v.job_prior)
packet:w_u8(v.job_id)
packet:w_bool(v.begin_job)
packet:w_stringZ(v.need_job)
end
n = 0
for k,v in pairs(self.dead_time) do
n = n + 1
end
packet:w_u8(n)
for k,v in pairs(self.dead_time) do
packet:w_u8(k)
utils.w_CTime(packet, v)
end
if self.base_on_actor_control ~= nil then
packet:w_bool(true)
self.base_on_actor_control:save(packet)
else
packet:w_bool(false)
end
if self.respawn_point then
packet:w_bool(true)
local n = 0
for k,v in pairs(self.already_spawned) do
n = n + 1
end
packet:w_u8(n)
for k,v in pairs(self.already_spawned) do
packet:w_stringZ(k)
packet:w_u8(v.num)
end
if self.last_respawn_update ~= nil then
packet:w_bool(true)
utils.w_CTime(packet, self.last_respawn_update)
else
packet:w_bool(false)
end
else
packet:w_bool(false)
end
if self.population < 0 then
abort("Smart_terrain [%s] population can't be less than zero!!!", self:name())
end
packet:w_u8(self.population)
set_save_marker(packet, "save", true, "se_smart_terrain")
end
-- восстановление
function se_smart_terrain:STATE_Read(packet, size)
cse_alife_smart_zone.STATE_Read(self, packet, size)
-- под LevelEditor не пытаться читать из пакета ничего
if editor() then
return
end
set_save_marker(packet, "load", false, "se_smart_terrain")
self:read_params()
-- Информацию о НПС, идущих в смарт
local n = packet:r_u8()
self.arriving_npc = {}
for i = 1,n do
local id = packet:r_u16()
self.arriving_npc[id] = false
end
-- Информацию о НПС в смарте
n = packet:r_u8()
--printf("load %s npc", tostring(n))
self.npc_info = {}
for i = 1,n do
local id = packet:r_u16()
--printf("__ id %s", tostring(id))
self.npc_info[id] = {}
local npc_info = self.npc_info[id]
npc_info.job_prior = packet:r_u8()
--printf("__ job_prior %s", tostring(npc_info.job_prior))
if npc_info.job_prior == 255 then
npc_info.job_prior = -1
end
npc_info.job_id = packet:r_u8()
--printf("__ job_id %s", tostring(npc_info.job_id))
if npc_info.job_id == 255 then
npc_info.job_id = -1
end
npc_info.begin_job = packet:r_bool()
--printf("__ begin_job %s", tostring(npc_info.begin_job))
npc_info.need_job = packet:r_stringZ()
end
n = packet:r_u8()
self.dead_time = {}
--printf("load %s dead_time", tostring(n))
for i =1,n do
local job_id = packet:r_u8()
--printf("__ job_id %s", tostring(job_id))
local dead_time = utils.r_CTime(packet)
self.dead_time[job_id] = dead_time
end
self.need_init_npc = true
if self.script_version > 9 then
if packet:r_bool() == true then
--self.base_on_actor_control
self.base_on_actor_control:load(packet)
end
end
local respawn_point = packet:r_bool()
--printf("LOAD RESPAWN %s", self:name())
if respawn_point then
n = packet:r_u8()
for i = 1, n do
local id = packet:r_stringZ()
local num = packet:r_u8()
self.already_spawned[id].num = num
end
if self.script_version > 11 then
local exist = packet:r_bool()
if exist then
self.last_respawn_update = utils.r_CTime(packet)
else
self.last_respawn_update = nil
end
end
end
self.population = packet:r_u8()
set_save_marker(packet, "load", true, "se_smart_terrain")
end
-- Инициализация НПС после загрузки.
function se_smart_terrain:init_npc_after_load()
local function find_job(jobs, npc_info)
for k,v in pairs(jobs) do
if v.jobs ~= nil then
find_job(v.jobs, npc_info)
else
if v.job_id == npc_info.job_id then
npc_info.job_link = v
v.npc_id = npc_info.se_obj.id
return
end
end
end
end
local sim = alife()
--printf("[%s] init_npc_after_load", self:name())
for k,v in pairs(self.arriving_npc) do
local sobj = sim:object(k)
if sobj ~= nil then
self.arriving_npc[k] = sobj
else
self.arriving_npc[k] = nil
end
end
for k,v in pairs(self.npc_info) do
local sobj = sim:object(k)
if sobj ~= nil then
local npc_info = self:fill_npc_info(sobj)
npc_info.job_prior = v.job_prior
npc_info.job_id = v.job_id
npc_info.begin_job = v.begin_job
npc_info.need_job = v.need_job
--Теперь надо найти данную работу и выставить ссылку на нее.
find_job(self.jobs, npc_info)
self.npc_info[k] = npc_info
if npc_info.job_link ~= nil then
self.npc_by_job_section[self.job_data[npc_info.job_link.job_id].section] = k
end
else
self.npc_info[k] = nil
end
end
end
--' Возвращает отформатированную строку свойств смарта
function se_smart_terrain:get_smart_props()
local props = smart_names.get_smart_terrain_name(self)
if(props==nil) or (_G.dev_debug) then
props = self:name().." ["..self.id.."]\\n"..
self.sim_type.."\\n"..
"squad_id = "..tostring(self.id).."\\n"..
"capacity = "..tostring(self.max_population).." ("..sim_board.get_sim_board():get_smart_population(self)..")\\n"
if self.respawn_point ~= nil and self.already_spawned ~= nil then
props = props.."\\nalready_spawned :\n"
for k,v in pairs(self.already_spawned) do
props = props.."["..k.."] = "..v.num.."("..xr_logic.pick_section_from_condlist(db.actor, nil,self.respawn_params[k].num)..")\\n"
end
if self.last_respawn_update then
props = props.."\\ntime_to_spawn:"..tostring(self.respawn_time - game.get_game_time():diffSec(self.last_respawn_update)).."\\n"
end
end
--' Добавляем информацию о находящихся в смарте отрядах
for k,v in pairs(sim_board.get_sim_board().smarts[self.id].squads) do
props = props .. tostring(v.id) .. "\\n"
end
end
return props
end
--' Отрисовка смарта на игровом поле
function se_smart_terrain:show()
local time = time_global()
if(self.showtime~=nil) and (self.showtime+200>=time) then
return
end
self.showtime = time
local player = self.player_name
local spot = "neutral"
if self.sim_avail == nil or xr_logic.pick_section_from_condlist(db.actor or alife():actor(), self, self.sim_avail) == "true" then
spot = "friend"
else
spot = "enemy"
end
if(self.smrt_showed_spot==spot) then
level.map_change_spot_hint(self.id, "alife_presentation_smart_"..self.sim_type.."_"..self.smrt_showed_spot, self:get_smart_props())
return
end
if(_G.dev_debug) then
if(self.smrt_showed_spot~=nil) then
level.map_remove_object_spot(self.id, "alife_presentation_smart_"..self.sim_type.."_"..self.smrt_showed_spot)
end
level.map_add_object_spot(self.id, "alife_presentation_smart_"..self.sim_type.."_"..spot, self:get_smart_props())
self.smrt_showed_spot = spot
else
if(self.smrt_showed_spot~=nil) and
(level.map_has_object_spot(self.id, "alife_presentation_smart_"..self.sim_type.."_"..self.smrt_showed_spot)~=0)
then
level.map_remove_object_spot(self.id, "alife_presentation_smart_base_"..self.smrt_showed_spot)
end
end
end
--' Обновление информации о смарте на игровом поле
function se_smart_terrain:refresh()
self:show()
end
--' Убирание отрисовки смарта на игровом поле
function se_smart_terrain:hide()
if self.smrt_showed_spot == nil then
return
end
level.map_remove_object_spot(self.id, "alife_presentation_smart_"..self.sim_type.."_"..self.smrt_showed_spot)
end
local function is_only_monsters_on_jobs(npc_info)
for k,v in pairs (npc_info) do
if v.is_monster == false then
return false
end
end
return true
end
-- Обновление.
-- В онлайне вызывается через binder.
-- Также может вызваться принудительно из xr_effects
function se_smart_terrain:update()
cse_alife_smart_zone.update( self )
if dev_debug then
self:refresh() -- Не забыть потом заремить
end
local current_time = time_global()
if simulation_objects.is_on_the_same_level(self, alife():actor()) then
local dist_to_actor = self.position:distance_to(alife():actor().position)
local old_dist_to_actor = (nearest_to_actor_smart.id == nil and nearest_to_actor_smart.dist) or alife():object(nearest_to_actor_smart.id).position:distance_to(alife():actor().position)
if dist_to_actor < old_dist_to_actor then
nearest_to_actor_smart.id = self.id
nearest_to_actor_smart.dist = dist_to_actor
end
end
-- Апдейт респауна отрядов симуляции.
if self.respawn_params ~= nil then
self:try_respawn()
end
if self.check_time~=nil and current_time < self.check_time then
return
end
--проверить есть ли кто-то в смарте, если есть и костры не включены то включить,
--еще проверить есть ли актер, чтоб была гарантия что костры проспонились...
if is_only_monsters_on_jobs(self.npc_info) and self.campfires_on then
bind_campfire.turn_off_campfires_by_smart_name(self:name())
self.campfires_on = false
elseif not is_only_monsters_on_jobs(self.npc_info) and not self.campfires_on then
bind_campfire.turn_on_campfires_by_smart_name(self:name())
self.campfires_on = true
end
if db.actor ~= nil then
local distance = db.actor:position():distance_to_sqr(self.position)
local idle_time = math.max(60, 0.003 * distance)
self.check_time = current_time + idle_time
else
self.check_time = current_time + 10
end
-- Проверяем, не истек ли запрет на занимание работы, на которой убили НПС
local current_time = game.get_game_time()
for k,v in pairs(self.dead_time) do
if current_time:diffSec(v) >= DEATH_IDLE_TIME then
self.dead_time[k] = nil
end
end
-- Перевыбор работ
self:update_jobs()
-- Апдейтим контрол реакции базы на игрока
if self.base_on_actor_control ~= nil then
self.base_on_actor_control:update()
end
-- Апдейт доступности для симуляции.
simulation_objects.get_sim_obj_registry():update_avaliability(self)
end
-- Переведение смарта в напряженное состояние
function se_smart_terrain:set_alarm()
self.smart_alarm_time = game.get_game_time()
end
-- Проверяет. а не прошел ли аларм в смарте
function se_smart_terrain:check_alarm()
if self.smart_alarm_time == nil then
return
end
if game.get_game_time():diffSec(self.smart_alarm_time) > 21600 then -- 6 Игровых часов
self.smart_alarm_time = nil
end
end
-- установить логику и сообщить смарту, что объект перешёл в онлайн.
-- вызывается из net_spawn() объектов
function setup_gulag_and_logic_on_spawn(obj, st, sobject, stype, loaded)
local sim = alife()
local sobject = alife():object(obj:id())
if sim ~= nil and sobject then
local strn_id = sobject.m_smart_terrain_id
printf( "setup_gulag_and_logic_on_spawn obj=%s, strn_id=%s, loaded=%s", obj:name(), tostring(strn_id), tostring(loaded))
if strn_id ~= nil and strn_id ~= 65535 then
local strn = sim:object(strn_id)
local need_setup_logic = (not loaded) and (strn.npc_info[obj:id()] and strn.npc_info[obj:id()].begin_job == true)
if need_setup_logic then
strn:setup_logic(obj)
else
xr_logic.initialize_obj(obj, st, loaded, db.actor, stype)
end
else
xr_logic.initialize_obj(obj, st, loaded, db.actor, stype)
end
else
xr_logic.initialize_obj(obj, st, loaded, db.actor, stype)
end
end
-- Убираем объект из смарта при смерти
function on_death(obj)
local sim = alife()
if sim then
local obj = sim:object(obj.id)
if obj == nil then return end
local strn_id = obj:smart_terrain_id()
if strn_id ~= 65535 then
printf("clear dead object %s", obj:name())
sim:object(strn_id):clear_dead(obj)
end
end
end
--***********************************************************************************************
--* SIMULATION_TARGET_SMART *
--***********************************************************************************************
-- Получить позицию, левел вертекс, гейм вертекс обьекта.
function se_smart_terrain:get_location()
return self.position, self.m_level_vertex_id, self.m_game_vertex_id
end
-- Достигнут ли я отрядом выбравшим меня как цель.
function se_smart_terrain:am_i_reached(squad)
local squad_pos, squad_lv_id, squad_gv_id = squad:get_location()
local target_pos, target_lv_id, target_gv_id = self:get_location()
if game_graph():vertex(squad_gv_id):level_id() ~= game_graph():vertex(target_gv_id):level_id() then
return false
end
if IsMonster(alife():object(squad:commander_id())) and squad:get_script_target() == nil then
return squad_pos:distance_to_sqr(target_pos) <= 25
end
return squad.always_arrived or squad_pos:distance_to_sqr(target_pos) <= self.arrive_dist^2
end
-- Вызывается 1 раз после достижения меня отрядом выбравшим меня как цель.
function se_smart_terrain:on_after_reach(squad)
for k in squad:squad_members() do
local obj = k.object
squad.board:setup_squad_and_group(obj)
end
squad.current_target_id = self.id
end
-- Вызывается 1 раз в момент выбора меня как цели.
function se_smart_terrain:on_reach_target(squad)
-- squad.sound_manager:set_storyteller(squad:commander_id())
-- squad.sound_manager:set_story("squad_begin_attack")
squad:set_location_types(self:name())
self.board:assign_squad_to_smart(squad, self.id)
for k in squad:squad_members() do
if db.offline_objects[k.id] ~= nil then
db.offline_objects[k.id] = {}
end
end
-- self.board:exit_smart(squad, squad.smart_id)
end
-- Возвращает CALifeSmartTerrainTask на меня, вызывается из smart_terrain:task()
function se_smart_terrain:get_alife_task()
return self.smart_alife_task
end
function smart_terrain_squad_count(board_smart_squads)
local count = 0
for k,v in pairs(board_smart_squads) do
if v:get_script_target() == nil then
count = count + 1
end
end
return count
end
function se_smart_terrain:sim_available()
if self.base_on_actor_control ~= nil and self.base_on_actor_control.status ~= smart_terrain_control.NORMAL then
return false
end
return true
end
local is_squad_monster =
{
["monster_predatory_day"] = true,
["monster_predatory_night"] = true,
["monster_vegetarian"] = true,
["monster_zombied_day"] = true,
["monster_zombied_night"] = true,
["monster_special"] = true
}
function surge_stats()
local sim_obj_registry = simulation_objects.get_sim_obj_registry().objects
local sim_squads = {
["zaton"] = {},
["jupiter"] = {},
["pripyat"] = {}
}
local sim_smarts = {
["zaton"] = {},
["jupiter"] = {},
["pripyat"] = {}
}
for k,v in pairs(sim_obj_registry) do
if v:clsid() == clsid.smart_terrain and tonumber(v.props["surge"]) > 0 then
local level_name = alife():level_name(game_graph():vertex(v.m_game_vertex_id):level_id())
if sim_smarts[level_name] ~= nil then
table.insert(sim_smarts[level_name], v)
end
end
if v:clsid() == clsid.online_offline_group_s then
local squad_params = sim_board.simulation_activities[v.player_id]
if squad_params ~= nil then
local smart_params = squad_params.smart.surge
if smart_params ~= nil then
local level_name = alife():level_name(game_graph():vertex(v.m_game_vertex_id):level_id())
if sim_squads[level_name] ~= nil then
table.insert(sim_squads[level_name], v)
end
end
end
end
end
local function print_smarts_and_squads_by_level(level_name)
printf("LEVEL: [%s]", level_name)
local max_capacity_total = 0
for i = 1, #sim_smarts[level_name] do
local smart = sim_smarts[level_name][i]
max_capacity_total = max_capacity_total + smart.max_population
local squad_count = smart_terrain_squad_count(sim_board.get_sim_board().smarts[smart.id].squads)
printf("smart: [%s] max_population [%d] squad_count [%d]", smart:name(),smart.max_population, squad_count)
end
printf("TOTAL: capacity total : [%d] squads total [%d]" , max_capacity_total, #sim_squads[level_name])
end
print_smarts_and_squads_by_level("zaton")
print_smarts_and_squads_by_level("jupiter")
print_smarts_and_squads_by_level("pripyat")
end
-- Мой прекондишн.
function se_smart_terrain:target_precondition(squad, need_to_dec_population)
if self.respawn_only_smart == true then
return false
end
local squad_count = smart_terrain_squad_count(self.board.smarts[self.id].squads)
if need_to_dec_population then
squad_count = squad_count - 1
end
if squad_count ~= nil and (self.max_population <= squad_count) then
--printf("smart terrain [%s] precondition returns false for squad [%s]", self:name(), squad:name())
-- if tonumber(self.props["surge"]) > 0 and xr_conditions.surge_started() then
-- printf("SURGE_SMART_STATS : smart [%s]\n max_population = %d \ squad_count = %d", self:name(), self.max_population, squad_count)
-- end
return false
end
local squad_params = sim_board.simulation_activities[squad.player_id]
if squad_params == nil or squad_params.smart == nil then
--printf("smart terrain [%s] precondition returns false for squad [%s]", self:name(), squad:name())
return false
end
if tonumber(self.props["resource"] )> 0 then
local smart_params = squad_params.smart.resource
if smart_params ~= nil and smart_params.prec(squad, self) then
return true
end
end
if tonumber(self.props["base"] )> 0 then
local smart_params = squad_params.smart.base
if smart_params ~= nil and smart_params.prec(squad, self) then
return true
end
end
if tonumber(self.props["lair"] )> 0 then
local smart_params = squad_params.smart.lair
if smart_params ~= nil and smart_params.prec(squad, self) then
return true
end
end
if tonumber(self.props["territory"] )> 0 then
local smart_params = squad_params.smart.territory
if smart_params ~= nil and smart_params.prec(squad, self) then
return true
end
end
if tonumber(self.props["surge"] )> 0 then
local smart_params = squad_params.smart.surge
if smart_params ~= nil and smart_params.prec(squad, self) then
return true
end
end
--printf("smart terrain [%s] precondition returns false for squad [%s]", self:name(), squad:name())
return false
--[[
local squad_count = smart_terrain_squad_count(self.board.smarts[self.id].squads)
if squad_count ~= nil and (self.max_population <= squad_count) then return false end
if squad.player_id == "stalker" and in_time_interval(9,19) and tonumber(self.props["resource"] )> 0 then
return true
end
--if squad.player_id ~= "monster_predatory" and squad.player_id ~= "monster_vegetarian" then
if not is_squad_monster[squad.player_id] then
if tonumber(self.props["base"]) > 0 and in_time_interval(20,8) then
return true
end
if tonumber(self.props["base"] ) > 0 and xr_conditions.surge_started() then
return true
end
else
if tonumber(self.props["lair"] ) > 0 and xr_conditions.surge_started() then
return true
end
if tonumber(self.props["lair"] ) > 0 and in_time_interval(7,20) then
return true
end
end
return false
]]
end
-- Посчитать мой приоритет для отряда.
function se_smart_terrain:evaluate_prior(squad)
return simulation_objects.evaluate_prior(self, squad)
end
-- Респаун симуляции.
function se_smart_terrain:check_respawn_params(respawn_params)
--printf("CHECK RESPAWN PARAMS %s", self:name())
self.respawn_params = {}
self.already_spawned = {}
self.respawn_point = true
if not self.ini:section_exist(respawn_params) then
abort("Wrong smatr_terrain respawn_params section [%s](there is no section)", respawn_params)
end
local n = self.ini:line_count(respawn_params)
if n == 0 then
abort("Wrong smatr_terrain respawn_params section [%s](empty params)", respawn_params)
end
for j=0,n-1 do
local result, prop_name, prop_condlist = self.ini:r_line(respawn_params,j,"","")
if not self.ini:section_exist(prop_name) then
abort("Wrong smatr_terrain respawn_params section [%s] prop [%s](there is no section)", respawn_params, prop_name)
end
local spawn_squads = utils.cfg_get_string(self.ini, prop_name, "spawn_squads", self, false, "", nil)
local spawn_num = utils.cfg_get_string(self.ini, prop_name, "spawn_num", self, false, "", nil)
if spawn_squads == nil then
abort("Wrong smatr_terrain respawn_params section [%s] prop [%s] line [spawn_squads](there is no line)", respawn_params, prop_name)
elseif spawn_num == nil then
abort("Wrong smatr_terrain respawn_params section [%s] prop [%s] line [spawn_num](there is no line)", respawn_params, prop_name)
end
spawn_squads = utils.parse_names(spawn_squads)
spawn_num = xr_logic.parse_condlist(nil, prop_name, "spawn_num", spawn_num)
self.respawn_params[prop_name] = {}
self.already_spawned[prop_name] = {}
self.respawn_params[prop_name].squads = spawn_squads
self.respawn_params[prop_name].num = spawn_num
self.already_spawned[prop_name].num = 0
end
end
function se_smart_terrain:call_respawn()
local available_sects = {}
printf("respawn called from smart_terrain [%s]", self:name())
for k,v in pairs(self.respawn_params) do
if tonumber(xr_logic.pick_section_from_condlist(db.actor, nil,v.num)) > self.already_spawned[k].num then
table.insert(available_sects,k)
end
end
if #available_sects > 0 then
local sect_to_spawn = available_sects[math.random(1,#available_sects)]
local sect_to_spawn_params = self.respawn_params[sect_to_spawn]
local squad = sect_to_spawn_params.squads[math.random(1,#sect_to_spawn_params.squads)]
squad = self.board:create_squad(self, squad)
squad.respawn_point_id = self.id
squad.respawn_point_prop_section = sect_to_spawn
self.board:enter_smart(squad, self.id)
for m in squad:squad_members() do
self.board:setup_squad_and_group(m.object)
end
self.already_spawned[sect_to_spawn].num = self.already_spawned[sect_to_spawn].num + 1
end
end
function se_smart_terrain:try_respawn()
--printf("TRY RESPAWN %s", self:name())
local curr_time = game.get_game_time()
if self.last_respawn_update == nil or curr_time:diffSec(self.last_respawn_update) > self.respawn_time then
self.last_respawn_update = curr_time
if self.sim_avail ~= nil and xr_logic.pick_section_from_condlist(db.actor or alife():actor(), self, self.sim_avail) ~= "true" then return end
local squad_count = smart_terrain_squad_count(self.board.smarts[self.id].squads)
if self.max_population <= squad_count then printf("%s cannot respawn due to squad_count %s of %s", self:name(), self.max_population, squad_count) return end
local dist_to_actor = alife():actor().position:distance_to_sqr(self.position)
if dist_to_actor < self.respawn_radius^2 then printf("%s cannot respawn due to distance", self:name()) return end
self:call_respawn()
end
end