1427 lines
52 KiB
Text
1427 lines
52 KiB
Text
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
|