local DEATH_IDLE_TIME = 10*60 -- секунд
local RESPAWN_IDLE = 1000 -- секунд игрового времени
local RESPAWN_RADIUS = 150 -- радиус респауна(если актер ближе, то не спаунить)
SMART_TERRAIN_SECT = "smart_terrain"
smart_terrains_by_name = {}
local locations_ini = ini_file("misc\\smart_terrain_masks.ltx")
local sm_dev_debug=true
db.stalker_smart={}
function printfb(a,...)
--debuger (CUT)
end
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 = {} -- Только идущие на работу.
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 sm_dev_debug 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 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)
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,a)
if self.b_registred == false then
table.insert(self.npc_to_register, obj)
return
end
if a == nil then
a = "Not Defined!"
end
self.population = self.population + 1
local obj_n=obj:name()
-- if string.find(self:name(), "zat_b104_zombied") then
printfb("[smart_terrain %s] register called obj=%s , population:%s , cf_s_s: %s", self:name(), obj_n, tostring(self.population), tostring(a))
-- end
db.stalker_smart[obj.id]=self.id
-- Только для монстров, чтобы ходили по смартам.
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
printfb("npc_info!")
self.npc_info[obj.id] = self:fill_npc_info(obj)
-- Затычка на случай если мы регистримся в смарт, из которого только что сами вынесли всех врагов.
self.dead_time = {}
-- тут надо найти чуваку работу
self:select_npc_job(self.npc_info[obj.id])
else
printfb("arriving!")
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,"added from smart_terrain")
end
self.npc_to_register = {}
end
-- отпустить npc
function se_smart_terrain:unregister_npc(obj,a,b)
--callstack()
if a == nil then
a = "Not Defined!"
end
self.population = self.population - 1
local obj_n=obj:name()
db.stalker_smart[obj.id]=nil
-- if string.find(self:name(), "zat_b104_zombied") then
printfb("[smart_terrain %s] unregister called obj=%s , population:%s , cf_s_s: %s", self:name(), obj_n, tostring(self.population), tostring(a))
-- end
if self.npc_info[obj.id] ~= nil then
-- TODO: Тут надо выгнать чувака с занимаемой им работы
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,sid)
printfb("sm:clear_dead: [%s][%s]",obj:name(),tostring(sid))
self:unregister_npc(obj,"unregister from smart_terrain clear_dead")
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]
-- Начинаем выполнять работу
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()]
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 (sm_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 = try_prt(props).."["..try_prt(k).."] = "..try_prt(v.num).."("..try_prt(xr_logic.pick_section_from_condlist(db.actor, nil,self.respawn_params[k].num))..")\\n"
end
if self.last_respawn_update then
props = try_prt(props).."\\ntime_to_spawn:"..try_prt(tostring(RESPAWN_IDLE - game.get_game_time():diffSec(self.last_respawn_update))).."\\n"
end
props = try_prt(props).."\\npopulation:"..try_prt(tostring(self.population)).."\\n"
end
--' Добавляем информацию о находящихся в смарте отрядах
for k,v in pairs(sim_board.get_sim_board().smarts[self.id].squads) do
props = try_prt(props)..try_prt(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(sm_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 sm_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
strn_id=db.stalker_smart[obj.id]
end
printfb("sm.on_death [%s][%s]",obj:name(),tostring(strn_id))
if strn_id ~= 65535 and strn_id ~= nil then
sim:object(strn_id):clear_dead(obj,strn_id)
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) > RESPAWN_IDLE 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 < RESPAWN_RADIUS^2 then printf("%s cannot respawn due to distance", self:name()) return end
self:call_respawn()
end
end