------------------------------ -- smart_terrain and gulag job system -- -- Revamped by Alundaio ------------------------------ -- How many jobs are checked per update per npc, for a job local STEP_SIZE = 5 local STEP_SIZE_OFFLINE = 1 local locations_ini = ini_file("misc\\smart_terrain_masks.ltx") nearest_to_actor_smart = {id = nil , dist = math.huge} dbg_hud = false local smart_sect = "smart_terrain" local squads_by_faction = { ["army"] = "army_sim_squad_novice, army_sim_squad_advanced, army_sim_squad_veteran", ["bandit"] = "bandit_sim_squad_novice, bandit_sim_squad_advanced, bandit_sim_squad_veteran", ["csky"] = "csky_sim_squad_novice, csky_sim_squad_advanced, csky_sim_squad_veteran", ["dolg"] = "duty_sim_squad_novice, duty_sim_squad_advanced, duty_sim_squad_veteran", ["ecolog"] = "ecolog_sim_squad_novice, ecolog_sim_squad_advanced, ecolog_sim_squad_veteran", ["freedom"] = "freedom_sim_squad_novice, freedom_sim_squad_advanced, freedom_sim_squad_veteran", ["killer"] = "merc_sim_squad_novice, merc_sim_squad_advanced, merc_sim_squad_veteran", ["monolith"] = "monolith_sim_squad_novice, monolith_sim_squad_advanced, monolith_sim_squad_veteran", ["stalker"] = "stalker_sim_squad_novice, stalker_sim_squad_advanced, stalker_sim_squad_veteran", ["renegade"] = "renegade_sim_squad_novice, renegade_sim_squad_advanced, renegade_sim_squad_veteran", ["greh"] = "greh_sim_squad_novice, greh_sim_squad_advanced, greh_sim_squad_veteran", ["isg"] = "isg_sim_squad_novice, isg_sim_squad_advanced, isg_sim_squad_veteran", } local s_find = string.find local _mej = { ['logic@bar_zastava_guard_1_walk'] = "logic@duty_guard1", ['logic@bar_zastava_guard_2_walk'] = "logic@duty_guard2", ['logic@bar_zastava_guard_3_walk'] = "logic@duty_guard3", ['logic@bar_zastava_guard_4_walk'] = "logic@duty_guard4", ['logic@bar_zastava_guard_5_walk'] = "logic@duty_guard5", ['logic@bar_zastava_guard_6_walk'] = "logic@duty_guard6", ['logic@bar_zastava_guard_7_walk'] = "logic@duty_guard7", ['logic@bar_zastava_guard_8_walk'] = "logic@duty_guard8", ['logic@bar_zastava_guard_9_walk'] = "logic@duty_guard9", ['logic@follower_bar_zastava_guard_1_walk'] = "logic@duty_guard1", ['logic@follower_bar_zastava_guard_2_walk'] = "logic@duty_guard2", ['logic@follower_bar_zastava_guard_4_walk'] = "logic@duty_guard4", ['logic@follower_bar_zastava_guard_5_walk'] = "logic@duty_guard5", ['logic@follower_bar_zastava_guard_6_walk'] = "logic@duty_guard6", ['logic@follower_bar_zastava_guard_7_walk'] = "logic@duty_guard7", ['logic@follower_bar_zastava_guard_8_walk'] = "logic@duty_guard8", } local function allowed(a) -- printf('allowed: %s',a.section) if not _mej[a.section] then return true end for k in pairs(db.storage) do if tonumber(k) and db.storage[k] and db.storage[k].section_logic == _mej[a.section] then -- printf('[%s] disallowed because [%s] (%s) taken', a.section, db.storage[k].section_logic, _mej[a.section]) return false end end return true end local function job_avail_to_npc(npc_info, job, smart) --[[ if (smart.dead_time[job.section]) then return false end --]] local precond = gulag_general.get_job_precondition(job) if (precond) then return allowed({ section = job.section}) and precond(npc_info.se_obj, smart, job, npc_info) end return true end function arrived_to_smart(se_obj, smart) --utils_obj.debug_nearest(se_obj,"check arrived_to_smart %s",smart:name()) local squad = se_obj.group_id and se_obj.group_id ~= 65535 and alife_object(se_obj.group_id) if (squad) then if (squad.current_action == 1 and squad.assigned_target_id == smart.id) then return true end return smart:am_i_reached(squad) end local cls = se_obj.clsid and se_obj:clsid() if not (cls) then return false end if (IsHelicopter(nil,cls)) then return true end if (se_obj.m_game_vertex_id == smart.m_game_vertex_id) then return true end local gg = game_graph() if (gg:vertex(se_obj.m_game_vertex_id):level_id() ~= gg:vertex(smart.m_game_vertex_id):level_id()) then return false end return se_obj.position:distance_to_sqr(smart.position) <= smart.arrive_dist^2 end ---------------------------------------------------------------------------------------------------------------------- -- ����� "se_smart_terrain". ������������ ��������� smart terrain � �������. ---------------------------------------------------------------------------------------------------------------------- class "se_smart_terrain" (cse_alife_smart_zone) function se_smart_terrain:__init(section) super(section) self.job_count = 0 self.npc_by_job_section = {} --self.dead_time = {} self.npc_info = {} self.arriving_npc = {} self.__campfire_check_time = game.get_game_time() --utils_data.debug_write(strformat("%s:%s Init",self:name(),self.id)) end function se_smart_terrain:on_before_register() -- persistent storage alife_storage_manager.get_se_obj_state(self,true) --RegisterScriptCallback("save_state",self) --utils_data.debug_write(strformat("%s:%s:on_before_register BEFORE",self:name(),self.id)) cse_alife_smart_zone.on_before_register(self) sim_board.get_sim_board():register_smart(self) --utils_data.debug_write(strformat("%s:%s:on_before_register AFTER",self:name(),self.id)) end actor_level = nil function se_smart_terrain:on_register() --utils_data.debug_write(strformat("%s:%s:ON_REGISTER start",self:name(),self.id)) cse_alife_smart_zone.on_register(self) db.add_smart_terrain(self) SendScriptCallback("server_entity_on_register",self,"se_smart_terrain") local sim = alife() local gg = game_graph() actor_level = actor_level or sim:level_name(gg:vertex(sim:actor().m_game_vertex_id):level_id()) local smart_level = sim:level_name(gg:vertex(self.m_game_vertex_id):level_id()) self.is_on_actor_level = actor_level == smart_level or nil self.smart_alife_task = CALifeSmartTerrainTask(self.m_game_vertex_id, self.m_level_vertex_id) --[[ Disable any smart terrain that is not on actor's level and not linked to actor's level through ai_tweaks\simulation_objects.ltx if not (self.is_on_actor_level) then if (DEACTIVATE_SIM_ON_NON_LINKED_LEVELS) then if not (s_find(simulation_objects.config:r_value(actor_level,"target_maps",0,""),smart_level)) then SIMBOARD.smarts[self.id] = nil self.disabled = true --utils_data.debug_write(strformat("%s:%s:ON_REGISTER END (DISABLED)",self:name(),self.id)) return end end end --]] simulation_objects.register(self) self:load_jobs() self.b_registred = true SIMBOARD:init_smart(self) self:register_delayed_npc() if (self.need_init_npc) then self.need_init_npc = nil self:init_npc_after_load() end self.check_time = time_global() --utils_data.debug_write(strformat("%s:%s:ON_REGISTER END",self:name(),self.id)) end function se_smart_terrain:on_unregister() SIMBOARD:unregister_smart(self) SendScriptCallback("server_entity_on_unregister",self,"se_smart_terrain") simulation_objects.unregister(self) db.del_smart_terrain(self) local m_data = alife_storage_manager.get_state() if (m_data.se_object) then m_data.se_object[self.id] = nil end cse_alife_smart_zone.on_unregister(self) end function se_smart_terrain:read_params() --utils_data.debug_write(strformat("%s:%s:se_smart_terrain:read_params BEFORE",self:name(),self.id)) local spawn_ini = self:spawn_ini() local filename = spawn_ini:r_string_ex(smart_sect,"cfg") if not (filename) then printe("!ERROR!: smart_terrain: no configuration file defined in spawn ini for %s",self:name()) self.disabled = true return end if filename then local fs = getFS() if fs:exist("$game_config$",filename) then self.ini = ini_file(filename) self.fname = filename else printe("!ERROR: There is no configuration file [%s] in smart_terrain [%s]", filename, self:name()) self.disabled = true return end end if not locations_ini:section_exist(self:name()) then printe("! SMART_TERRAIN [%s] has no terrain_mask section in smart_terrain_masks.ltx!!!",self:name()) end local ini = self.ini self.arrive_dist = ini:r_float_ex(smart_sect,"arrive_dist") or 50 self.death_idle_time = ini:r_float_ex(smart_sect,"death_idle_time") or 600 self.def_restr = ini:r_string_ex(smart_sect,"def_restr") -- default out_restrictor for npc on jobs here self.default_faction = ini:r_string_ex(smart_sect,"default_faction") self.faction_controlled = ini:r_string_ex(smart_sect,"faction_controlled") self.max_population = tonumber(xr_logic.pick_section_from_condlist(get_story_object("actor"), nil, ini:r_string_to_condlist(smart_sect,"max_population")) or 0) or 0 self.min_population = tonumber(xr_logic.pick_section_from_condlist(get_story_object("actor"), nil, ini:r_string_to_condlist(smart_sect,"min_population")) or 0) or 0 self.respawn_idle = ini:r_float_ex(smart_sect,"respawn_idle") or 43200 self.respawn_only_level = ini:r_bool_ex(smart_sect,"respawn_only_level",false) self.respawn_only_smart = ini:r_bool_ex(smart_sect,"respawn_only_smart",false) self.respawn_point = false self.respawn_radius = ini:r_float_ex(smart_sect,"respawn_radius") or 150 self.safe_restr = ini:r_string_ex(smart_sect,"safe_restr") -- npc with jobs inside are invulnerable self.spawn_point = ini:r_string_ex(smart_sect,"spawn_point") self.squad_id = ini:r_float_ex(smart_sect,"squad_id") or 0 -- for quest needs self.ignore_zone = ini:r_string_ex(smart_sect,"ignore_zone") self.alarm_start_sound = ini:r_string_to_condlist(smart_sect,"alarm_start_sound") self.alarm_stop_sound = ini:r_string_to_condlist(smart_sect,"alarm_stop_sound") self.alarm_length = ini:r_float_ex(smart_sect,"alarm_length") or 30000 if (self.faction_controlled) then local spawn_num_condlist = ini:r_string_to_condlist(smart_sect,"faction_respawn_num") if (spawn_num_condlist) then self.respawn_params = self.respawn_params or {} self.already_spawned = self.already_spawned or {} self.respawn_point = true local p = parse_names(self.faction_controlled) if (p) then for i,faction in pairs(p) do local prop_name = "faction_controlled_"..faction if (squads_by_faction[faction]) then self.respawn_params[prop_name] = self.respawn_params[prop_name] or {} self.respawn_params[prop_name].num = spawn_num_condlist self.respawn_params[prop_name].squads = parse_names(squads_by_faction[faction]) self.respawn_params[prop_name].faction = faction self.already_spawned[prop_name] = self.already_spawned[prop_name] or { num = 0 } end end end end return --utils_data.debug_write(strformat("%s:%s:se_smart_terrain:read_params AFTER 1",self:name(),self.id)) end local respawn_params = ini:r_string_ex(smart_sect,"respawn_params") if not (respawn_params) then return --utils_data.debug_write(strformat("%s:%s:se_smart_terrain:read_params AFTER 2",self:name(),self.id)) end if not ini:section_exist(respawn_params) then printf("%s Wrong smart_terrain respawn_params section [%s](there is no section)",self:name(),respawn_params) return end local n = ini:line_count(respawn_params) if n == 0 then printf("%s Wrong smart_terrain respawn_params section [%s](empty params)", self:name(), respawn_params) return end self.respawn_params = self.respawn_params or {} self.already_spawned = self.already_spawned or {} self.respawn_point = true for j=0,n-1 do local result, prop_name, prop_condlist = ini:r_line(respawn_params,j,"","") if not ini:section_exist(prop_name) then printe("!ERROR: %s Wrong smatr_terrain respawn_params section [%s] prop [%s](there is no section)",self:name(), respawn_params, prop_name) else local spawn_num_condlist = ini:r_string_to_condlist(prop_name,"spawn_num") if (spawn_num_condlist) then self.respawn_params[prop_name] = self.respawn_params[prop_name] or {} self.respawn_params[prop_name].num = spawn_num_condlist self.respawn_params[prop_name].squads = ini:r_list(prop_name,"spawn_squads") self.respawn_params[prop_name].helicopter = ini:r_list(prop_name,"spawn_helicopter") self.already_spawned[prop_name] = self.already_spawned[prop_name] or { num = 0 } end end end return --utils_data.debug_write(strformat("%s:%s:se_smart_terrain:read_params AFTER 5",self:name(),self.id)) end function se_smart_terrain:fill_npc_info(obj) --utils_data.debug_write(strformat("%s:%s:fill_npc_info %s",self:name(),self.id,obj and obj:name())) local cls = obj.clsid and obj:clsid() if not (cls) then return printf("fill_npc_info invalid clsid for %s",obj and obj:name()) end local stype = IsStalker(nil,cls) and 0 or IsMonster(nil,cls) and 1 or IsHelicopter(nil,cls) and 3 or nil if not (stype) then return printf("fill_npc_info invalid stype for %s [clsid=%s]",obj and obj:name(),cls) end local npc_info = {} npc_info.se_obj = obj npc_info.need_job = "nil" npc_info.job = nil npc_info.begin_job = false npc_info.stype = stype return npc_info end function se_smart_terrain:register_delayed_npc() if (self.npc_to_register) then for k,v in pairs(self.npc_to_register) do --utils_data.debug_write(strformat("%s:%s:register_delayed_npc %s",self:name(),self.id,v and v:name())) self:register_npc(v) end self.npc_to_register = nil end end function se_smart_terrain:register_npc(obj) --utils_data.debug_write(strformat("%s:%s:register_npc %s",self:name(),self.id,obj and obj:name())) -- ensure registration is clean if (self.arriving_npc[obj.id] or self.npc_info[obj.id]) then --self.arriving_npc[obj.id] = nil --self:clear_job(obj.id,true) return end local cls = obj.clsid and obj:clsid() if not (cls) then return printf("register_npc no clsid! %s",obj and obj:name()) end -- dynamic companions cannot register if (IsStalker(nil,cls) and alife():has_info(obj.id,"npcx_is_companion")) then return end --smart not yet registered if not (self.b_registred) then if not (self.npc_to_register) then self.npc_to_register = {} end self.npc_to_register[obj.id] = obj --table.insert(self.npc_to_register, obj) return end if (IsMonster(nil,cls)) 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 = empty_table(self.dead_time) self:select_npc_job(self.npc_info[obj.id],true) else self.arriving_npc[obj.id] = obj end end function se_smart_terrain:only_faction_on_jobs(faction) for id,npc_info in pairs(self.npc_info) do local se_obj = npc_info.se_obj local comm = se_obj and IsStalker(nil,se_obj:clsid()) and se_obj:community() if (comm and comm ~= zombied and comm ~= faction) then return false end end return true end function se_smart_terrain:unregister_npc(obj) --utils_data.debug_write(strformat("%s:%s:unregister_npc %s",self:name(),self.id,obj and obj:name())) self.arriving_npc[obj.id] = nil if (obj.clear_smart_terrain) then obj:clear_smart_terrain() end self:clear_job(obj.id,true) local st = db.storage[obj.id] if (st and st.object) then local cls = obj.clsid and obj:clsid() if not (cls) then return end local stype = IsStalker(nil,cls) and 0 or IsMonster(nil,cls) and 1 or IsHelicopter(nil,cls) and 3 or nil if not (stype) then return end xr_logic.initialize_obj(st.object, st, false, db.actor, stype) end --printf("self.npc_info[obj.id] = nil !!! obj.id=%s [%s]", obj.id,obj:name()) end function se_smart_terrain:clear_dead(obj) --utils_data.debug_write(strformat("%s:clear_dead %s",self:name(),obj and obj:name())) self.arriving_npc[obj.id] = nil if (obj.clear_smart_terrain) then obj:clear_smart_terrain() end self:clear_job(obj.id,true) --printf("clear_dead():self.npc_info[obj.id] = nil !!! obj.id=%s [%s]", obj.id,obj:name()) end local function validate_patrol_path(path_name,gg) local p = patrol(path_name) local ret = true if (p) then local cnt = p:count() for i=0,cnt-1 do if not (gg:valid_vertex_id(p:game_vertex_id(i))) then printf("validate_patrol_path WARNING: %s: invalid game_vertex_id for point %s",path_name,i) ret = false end end else printf("validate_patrol_path ERROR: %s: path does not exist",path_name) return false end return ret end function se_smart_terrain:load_jobs() if (self.disabled) then return end if not (self.is_on_actor_level) then return end --utils_data.debug_write(strformat("%s:%s:load_jobs %s Before",self:name(),self.id,obj and obj:name())) -- load job data from dynamic ltx from gulag_general.script gulag_general.load_job(self) if not (self.ltx) then printe("!CRITICAL ERROR: %s does not have a dynamic ltx!",self:name()) return end --utils_data.debug_write(strformat("%s:load_jobs %s After",self:name(),obj and obj:name())) -- Setup Alife Tasks for smart's gulag jobs local job,active_section,section,ltx, path_field, path_name, smartcover, smartcover_name, prefix_name, job_type local gg, CALifeSmartTerrainTask,level = game_graph(),CALifeSmartTerrainTask,level local table_of_jobs = {self.stalker_jobs,self.monster_jobs,self.heli_jobs} local tbl for n=1,#table_of_jobs do tbl = table_of_jobs[n] if (tbl and #tbl > 0) then for i=1,#tbl do job = tbl[i] section = job.section ltx = job.ltx or self.ltx if not (ltx:line_exist(section,"active")) then printf("gulag: ltx=%s no 'active' in section %s", self.ltx_name, section) else if not (job.exclusive) then self.job_count = self.job_count + 1 end active_section = ltx:r_string_ex(section, "active") if not (active_section) then abort("%s has no active = in logic %s",self:name(),self.ltx_name) end job_type = gulag_general.get_job_type(job) job.alife_task = self.smart_alife_task if (job_type == "path_job" or job_type == "heli_path_job") then path_field = "nil" path_field = (ltx:line_exist(active_section,"path_walk") and "path_walk" or ltx:line_exist(active_section,"path_main") and "path_main" or ltx:line_exist(active_section,"path_home") and "path_home" or ltx:line_exist(active_section,"center_point") and "center_point" or ltx:line_exist(active_section,"path_move") and "path_move" or "nil") if (path_field == "nil") then printf("smart_terrain.load_jobs(): Cannot find path_field in section %s. ini_path=%s",active_section,job.ini_path) else prefix_name = gulag_general.get_job_prefix_name(job) if (prefix_name) then path_name = (prefix_name == "nil" and "" or prefix_name) .. ltx:r_string_ex(active_section, path_field) else path_name = self:name() .. "_" .. ltx:r_string_ex(active_section, path_field) end if (path_field == "center_point") then if level.patrol_path_exists(path_name.."_task") then path_name = path_name.."_task" end end if (level.patrol_path_exists(path_name)) then if not (job_type == "heli_path_job") then if (validate_patrol_path(path_name,gg)) then job.alife_task = CALifeSmartTerrainTask(path_name) end end else printf("%s:load_jobs() There is no such patrol path %s",self:name(),path_name) end end elseif (job_type == "heli_hide_job") then -- do nothing job.alife_task not needed elseif (job_type == "smartcover_job") then smartcover_name = ltx:r_string_ex(active_section, "cover_name") smartcover = se_smart_cover.registered_smartcovers[smartcover_name] if not (smartcover) then printf("There is an exclusive job with wrong smatrcover name [%s] smartterrain [%s]", tostring(smartcover_name), self:name()) else job.alife_task = CALifeSmartTerrainTask(smartcover.m_game_vertex_id, smartcover.m_level_vertex_id) end end if (job.alife_task) then -- if a single job is failed, ignore and fill rest of job table job.game_vertex_id = job.alife_task:game_vertex_id() job.level_id = gg:vertex(job.game_vertex_id):level_id() job.position = job.alife_task:position() end end end end end end function se_smart_terrain:clear_job(id,rem) --utils_data.debug_write(strformat("%s:clear_job %s",self:name(),id)) if (self.npc_info[id]) then if (self.npc_info[id].job) then for sec,npcid in pairs(self.npc_by_job_section) do if (id == npcid) then self.npc_by_job_section[sec] = nil end end end if (rem) then self.npc_info[id] = nil end end end function se_smart_terrain:update_jobs() if (self.disabled) then return end if not (self.is_on_actor_level) then return end -- Fill NPC Job Info and Give them a job for id,se_obj in pairs(self.arriving_npc) do if (se_obj) then if (arrived_to_smart(se_obj,self)) then self.npc_info[id] = self:fill_npc_info(se_obj) self.arriving_npc[id] = nil end elseif (se_obj == false) then local sobj = alife_object(id) if (sobj) then self.arriving_npc[id] = sobj else self.arriving_npc[id] = nil end end end local gg = game_graph() self.level_id = self.level_id or gg:vertex(self.m_game_vertex_id):level_id() -- Update Jobs for existing NPCs for id,info in pairs(self.npc_info) do local se_obj = info.se_obj if (se_obj) then --if (self.level_id == gg:vertex(se_obj.m_game_vertex_id):level_id()) then self:select_npc_job(info) -- else -- self.arriving_npc[id] = se_obj -- self:clear_job(id,true) -- end else self:clear_job(id,true) end end end function se_smart_terrain:select_npc_job(npc_info,now,surge_started) if (self.disabled or not self.is_on_actor_level) then return end --utils_data.debug_write(strformat("%s:select_npc_job %s",self:name())) if not (npc_info and npc_info.se_obj and npc_info.stype) then return printf("%s no npc_info!",self:name()) end -- reference job table according to race local jobs = npc_info.stype == 0 and self.stalker_jobs or npc_info.stype == 1 and self.monster_jobs or npc_info.stype == 3 and self.heli_jobs if not (jobs) then return printf("%s no job table for %s [stype = %s]",self:name(),npc_info.se_obj:name(),npc_info.stype) end local new_job local npc_by_job_section = self.npc_by_job_section -- If the NPC has an existing job link, validate it and iterate job table -- for a higher priority job according to defined STEP_SIZE if (npc_info.job) then -- Make sure current job is still available if (npc_by_job_section[npc_info.job.section] == npc_info.se_obj.id) then -- Make sure current job is still available if (job_avail_to_npc(npc_info,npc_info.job,self)) then if (npc_info.current_index == nil or now) then npc_info.current_index = 1 end local get_job_prior = gulag_general.get_job_prior local itr_job local npc_id local step = 1 while (new_job == nil and step <= (now and #jobs or st and STEP_SIZE or STEP_SIZE_OFFLINE)) do step = step + 1 if (npc_info.current_index > #jobs) then npc_info.current_index = 1 break end -- Step through job table one step at a time looking for a higher prior job itr_job = jobs[npc_info.current_index] npc_info.current_index = npc_info.current_index + 1 npc_id = npc_by_job_section[itr_job.section] -- Check empty job --if (npc_id == nil or npc_id == npc_info.se_obj.id) then if (npc_id == nil) then -- Find only higher priority jobs if already linked to a job if (get_job_prior(itr_job) > get_job_prior(npc_info.job)) then if (job_avail_to_npc(npc_info, itr_job, self)) then -- Only take this higher priority job if when there is either no surge or if job is not in surge cover during surge; takes exclusive no matter what if (itr_job.exclusive or not xr_conditions.surge_started() or not surge_manager.job_in_surge_cover(npc_info.se_obj,npc_info.job)) then new_job = itr_job npc_info.current_index = 1 end end end end end else -- Job is no longer available, unlink job info for sec,npcid in pairs(npc_by_job_section) do if (npcid == npc_info.se_obj.id) then npc_by_job_section[sec] = nil end end npc_info.job = nil npc_info.current_index = 1 end else -- Job is not linked properly, npc_id doesn't match owner se_obj for sec,npcid in pairs(npc_by_job_section) do if (npcid == npc_info.se_obj.id) then npc_by_job_section[sec] = nil end end npc_info.job = nil npc_info.current_index = 1 end end if not (npc_info.job) then if (npc_info.current_index == nil or now) then npc_info.current_index = 1 end local itr_job,npc_id local step = 1 local st = db.storage[npc_info.se_obj.id] local STEP_SIZE_NO_JOB = #jobs while (new_job == nil and step <= (now and STEP_SIZE_NO_JOB or st and STEP_SIZE_NO_JOB or STEP_SIZE_OFFLINE)) do step = step + 1 if (npc_info.current_index > STEP_SIZE_NO_JOB) then npc_info.current_index = 1 break end -- Step through job table one step at a time looking for a high prior job itr_job = jobs[npc_info.current_index] npc_info.current_index = npc_info.current_index + 1 npc_id = npc_by_job_section[itr_job.section] -- validate existing job link if (npc_id and not self.npc_info[npc_id]) then npc_by_job_section[itr_job.section] = nil end -- Check empty job or re-take current job if (npc_id == nil or npc_id == npc_info.se_obj.id) and (job_avail_to_npc(npc_info, itr_job, self)) then new_job = itr_job npc_info.current_index = 1 end end if not (new_job) then return end end local id = npc_info.se_obj.id -- newly selected job if (new_job and new_job ~= npc_info.job) then -- Unassign npc_id from old job and unreference job section for sec,npcid in pairs(npc_by_job_section) do if (npcid == id) then npc_by_job_section[sec] = nil end end -- setup table that references NPCs by their job section npc_by_job_section[new_job.section] = id -- link up NPC info and references npc_info.job = new_job npc_info.begin_job = false -- grab storage and ensure object is online. local st = self.online and db.storage[id] -- If NPC has storage, it is online, so switch logic if (st and st.object) then --xr_logic.switch_to_section(st.object, self.ltx, "nil") npc_info.begin_job = true empty_table(db.offline_objects[id]) self:setup_logic(st.object) return true end end if (npc_info.begin_job ~= true and npc_info.job) then -- grab storage and ensure object is online. local st = self.online and db.storage[id] -- If NPC has storage it is online, so switch logic if (st and st.object) then npc_info.begin_job = true empty_table(db.offline_objects[id]) self:setup_logic(st.object) return true end end end function se_smart_terrain:getJob(obj_id) return self.npc_info[obj_id] and self.npc_info[obj_id].job or nil 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) if (self.disabled or not self.is_on_actor_level) then return end local npc_id = npc:id() local npc_info = self.npc_info[npc_id] local changing_npc_id = self.npc_by_job_section[npc_info.need_job] -- unlink old job self:clear_job(npc_id) -- No changing npc, find new job if changing_npc_id == nil then self.npc_info[npc_id].job = nil self:select_npc_job(self.npc_info[npc_id]) return end -- No changing npc, find new job if self.npc_info[changing_npc_id] == nil then self.npc_info[npc_id].job = nil self:select_npc_job(self.npc_info[npc_id]) return end -- Changing NPC doesn't have a linked job, find us both jobs if not (self.npc_info[changing_npc_id].job) then self.npc_info[npc_id].job = nil self:select_npc_job(self.npc_info[npc_id]) self:select_npc_job(self.npc_info[changing_npc_id]) return end -- take job from changing_npc_id npc_info.job = self.npc_info[changing_npc_id].job self.npc_by_job_section[npc_info.job.section] = npc_id npc_info.begin_job = true npc_info.need_job = "nil" -- setup logic local st = db.storage[npc_id] if (st and st.object) then self:setup_logic(st.object) end -- tell changing_npc_id to GTFO and get a job self.npc_info[changing_npc_id].job = nil self:select_npc_job(self.npc_info[changing_npc_id]) end --[[ function se_smart_terrain:save_state(m_data) --utils_data.debug_write(strformat("se_smart_terrain:save_state BEFORE %s %s",self:name(),self.id)) if not (m_data.smart_terrains) then m_data.smart_terrains = {} end --printf("m_data.smart_terrains = %s name=%s",m_data.smart_terrains ~= nil,self:name()) m_data.smart_terrains[self:name()] = {} local t = m_data.smart_terrains[self:name()] for id,v in pairs(self.arriving_npc) do if not (t.arriving_npc) then t.arriving_npc = {} end table.insert(t.arriving_npc,id) end for id,info in pairs(self.npc_info) do if not (t.npc_info) then t.npc_info = {} end t.npc_info[id] = {} t.npc_info[id].job_section = info.job and info.job.section t.npc_info[id].begin_job = info.begin_job t.npc_info[id].need_job = info.need_job end if not (is_empty(self.already_spawned)) then t.already_spawned = self.already_spawned end t.last_respawn_update = self.last_respawn_update local tim = (self.smart_alarm_time or time_global()) - time_global() if (tim > 1000) then -- don't bother storing such a low value t.smart_alarm_time = tim end --utils_data.debug_write(strformat("se_smart_terrain:save_state AFTER %s",self:name())) end function se_smart_terrain:load_state(m_data) --printf("load state %s",self:name()) if not (m_data.smart_terrains and m_data.smart_terrains[self:name()]) then return end --utils_data.debug_write(strformat("se_smart_terrain:load_state BEFORE %s",self:name())) self.need_init_npc = true if (m_data.smart_terrains[self:name()].arriving_npc) then for i,id in pairs(m_data.smart_terrains[self:name()].arriving_npc) do self.arriving_npc[id] = false end end self.load_info = m_data.smart_terrains[self:name()].npc_info or {} self.already_spawned = m_data.smart_terrains[self:name()].already_spawned or self.already_spawned self.last_respawn_update = m_data.smart_terrains[self:name()].last_respawn_update self.smart_alarm_time = (m_data.smart_terrains[self:name()].smart_alarm_time or time_global()) - time_global() m_data.smart_terrains[self:name()] = nil --utils_data.debug_write(strformat("se_smart_terrain:load_state AFTER %s",self:name())) end --]] --******************************************************* -- ����/���� --******************************************************* function se_smart_terrain:STATE_Write(packet) cse_alife_smart_zone.STATE_Write(self, packet) -- if (USE_MARSHAL) then -- return -- end --set_save_marker(packet, "save", false, "se_smart_terrain") -- ���������� � ���, ������ � ����� local n = table.size(self.arriving_npc) utils_data.w_stpk(packet,"u8",n,"arriving_npc count") for k,v in pairs(self.arriving_npc) do utils_data.w_stpk(packet,"u16",k,"arriving_npc id") end -- ���������� � ��� � ������ n = table.size(self.npc_info) --printf("%s:STATE_Write JobCount=%s",self:name(),n) utils_data.w_stpk(packet,"u8",n,"npc_info count") for id,info in pairs(self.npc_info) do utils_data.w_stpk(packet,"u16",id,"npc_info id") utils_data.w_stpk(packet,"stringZ",info.job and info.job.section or "nil","npc_info.job_section") utils_data.w_stpk(packet,"bool",info.begin_job,"npc_info.begin_job") utils_data.w_stpk(packet,"stringZ",tostring(info.need_job),"npc_info.need_job") end if self.respawn_point then utils_data.w_stpk(packet,"bool",true,"respawn_point") local n = table.size(self.already_spawned) utils_data.w_stpk(packet,"u8",n,"already_spawned count") for k,v in pairs(self.already_spawned) do utils_data.w_stpk(packet,"stringZ",k,"already_spawned section") utils_data.w_stpk(packet,"u8",v.num,"already_spawned count") end utils_data.w_stpk(packet,"CTime",self.last_respawn_update,"last_respawn_update CTime") else utils_data.w_stpk(packet,"bool",false,"respawn_point") end local tg = time_global() utils_data.w_stpk(packet,"s32",(self.smart_alarm_time or tg) - tg,"smart_alarm_time") --set_save_marker(packet, "save", true, "se_smart_terrain") end function se_smart_terrain:STATE_Read(packet, size) --utils_data.debug_write(strformat("%s:STATE_READ start",self:name())) cse_alife_smart_zone.STATE_Read(self, packet, size) self:read_params() -- if (USE_MARSHAL) then -- self:load_state(alife_storage_manager.get_state()) -- return -- end self.need_init_npc = true --set_save_marker(packet, "load", false, "se_smart_terrain") -- ���������� � ���, ������ � ����� local n = packet:r_u8() self.arriving_npc = {} for i = 1,n do local id = packet:r_u16() if (id < 65535) then self.arriving_npc[id] = false end end n = packet:r_u8() self.load_info = {} local id for i = 1,n do id = packet:r_u16() self.load_info[id] = {} self.load_info[id].job_section = packet:r_stringZ() self.load_info[id].begin_job = packet:r_bool() self.load_info[id].need_job = packet:r_stringZ() end if (self.load_info[65535]) then printf("%s:STATE_Read: invalid id in npc_info table. Check for save corruption or error in smart_terrain job system.") self.load_info[65535] = nil end --[[ n = packet:r_u8() empty_table(self.dead_time) for i =1,n do local sec = packet:r_stringZ() local gt = utils_data.r_CTime(packet) if (sec and sec ~= "nil" and sec ~= "") then self.dead_time[sec] = gt 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() if (self.already_spawned and id and id ~= "nil" and id ~= "") then -- technically it's an error but it's makes save compatible if change respawn if not (self.already_spawned[id]) then self.already_spawned[id] = {} end --[[ if (self.already_spawned[id] == nil) then printe("!ERROR! %s:STATE_Read already_spawned %s is nil!",self:name(),id) local str = "" for k,v in pairs(self.already_spawned) do str = str .. k .. ", " end printe("!ERROR! %s.already_spawned[%s]",self:name(),str) end --]] self.already_spawned[id].num = num end end self.last_respawn_update = utils_data.r_CTime(packet) end if (packet:r_elapsed() ~= 0) then local tmr = packet:r_s32() if (tmr > 0 and tmr <= self.alarm_length) then self.smart_alarm_time = time_global() + tmr end end --set_save_marker(packet, "load", true, "se_smart_terrain") --utils_data.debug_write(strformat("%s:STATE_READ end",self:name())) end -- Setup NPC jobs from loaded savegames. Use unique ids generated -- by the gulag_general.script for each smart job to retake job function se_smart_terrain:init_npc_after_load() if (self.disabled and not self.is_on_actor_level) then return end local sim = alife() -- validate and fill saved npc_info; find job by saved uid for id, info in pairs(self.load_info) do local sobj = sim:object(id) local cls = sobj and sobj:clsid() if (cls) and (IsStalker(nil,cls) or IsMonster(nil,cls) or IsHelicopter(nil,cls)) then local new_info = self:fill_npc_info(sobj) new_info.job = self:find_job_by_section(info.job_section) if (new_info.job) then self.npc_by_job_section[new_info.job.section] = id new_info.begin_job = info.begin_job new_info.need_job = info.need_job end self.npc_info[id] = new_info else --self.npc_info.job = self:find_job_by_section(info.job_section) self:clear_job(id,true) end end -- Here we setup arriving npcs from loaded savegame for id,v in pairs(self.arriving_npc) do local se_obj = sim:object(id) if (se_obj and arrived_to_smart(se_obj,self)) then self.npc_info[se_obj.id] = self:fill_npc_info(se_obj) self:select_npc_job(self.npc_info[se_obj.id],true) self.arriving_npc[se_obj.id] = nil end end self.load_info = nil end function se_smart_terrain:stayed_squad_count() local smrt = SIMBOARD.smarts[self.id] if (smrt) then local count = 0 for k,v in pairs(smrt.squads) do local squad = alife_object(k) if (squad and squad.current_target_id and squad.current_target_id == self.id) then count = count + 1 end end return count end return 0 end function se_smart_terrain:get_smart_props() if (self.disabled) then return "deactivated\\n\\n"..self:name().." ["..self.id.."]\\n".."squad_id = "..tostring(self.squad_id) end local props = (not self.disabled) and get_smart_terrain_name(self) if(props==nil) or (DEV_DEBUG and dbg_hud) then if (self.disabled) then return "deactivated\\n\\n"..self:name().." ["..self.id.."]\\n".."squad_id = "..tostring(self.squad_id) end local board = SIMBOARD local squad_count = board.smarts[self.id].population --smart_terrain_squad_count(board.smarts[self.id].squads) props = self:name().." ["..self.id.."]\\n" if (self.faction_controlled) then props = props .. "Faction_controlled=" .. tostring(self.faction_controlled) .. "\\nFaction_war_in_progress=" .. tostring(self.faction_war_in_progress) .. "\\n" props = props .. "Controlling_faction=".. tostring(self.faction) .. "\\n" end props = props .. "squad_id = "..tostring(self.squad_id).."\\n".."SimCapacity="..squad_count.." of "..tostring(self.max_population).."\\n" if self.respawn_point and self.already_spawned then props = props.."\\nAlready_spawned :\n" for k,v in pairs(self.already_spawned) do if (v.num and self.respawn_params[k] and self.respawn_params[k].num) then props = props.."["..k.."] = "..v.num.."("..(xr_logic.pick_section_from_condlist(db.actor, self,self.respawn_params[k].num) or "0")..")\\n" end end end if not (self.respawn_params) then props = props .. "[smart has no respawn params]\\n" end if self.last_respawn_update then props = props.."\\nTime_to_spawn:"..tostring(self.respawn_idle - game.get_game_time():diffSec(self.last_respawn_update)).."\\n" end --' ��������� ���������� � ����������� � ������ ������� for k,v in pairs(SIMBOARD.smarts[self.id].squads) do props = props .. tostring(k) .. "\\n" end if (self.props) then for prop,val in pairs(self.props) do if (val > 0) then props = props .. prop .. " = " .. val .. "\\n" end end end props = props .. "\\nstayed_squad_count = " .. tostring(self:stayed_squad_count()) end return props end function se_smart_terrain:show() if DEV_DEBUG and (dbg_hud) then if (level.map_has_object_spot(self.id,"alife_presentation_smart_default_neutral") == 0) then level.map_add_object_spot(self.id, "alife_presentation_smart_default_neutral", self:get_smart_props()) else level.map_change_spot_hint(self.id, "alife_presentation_smart_default_neutral", self:get_smart_props()) end else if (level.map_has_object_spot(self.id,"alife_presentation_smart_default_neutral") ~= 0) then level.map_remove_object_spot(self.id, "alife_presentation_smart_default_neutral") end end 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_default_"..self.smrt_showed_spot) end local function is_only_monsters_on_jobs(npc_info) for k,v in pairs(npc_info) do if not (v.stype == 1) then return false end end return true end function se_smart_terrain:check_smart_faction() local factions_present = {} for k,v in pairs(self.npc_info) do if (v.se_obj and IsStalker(nil,v.se_obj:clsid())) then factions_present[v.se_obj:community()] = true end end self.faction_war_in_progress = false local last_faction -- check if factions are hostile to eachother for f,v in pairs(factions_present) do last_faction = f for ff,vv in pairs(factions_present) do if (f ~= ff) then if (game_relations.is_factions_enemies(f, ff)) then self.faction_war_in_progress = true break end end end end -- determine faction if (self.faction_war_in_progress == false) then self.faction = self.faction and factions_present[self.faction] and self.faction or last_faction or self.default_faction end end function se_smart_terrain:update() cse_alife_smart_zone.update( self ) --utils_data.debug_write(strformat("%s:update ",self:name())) self:show() if (self.disabled) then return end local sim = alife() local se_actor = sim:actor() self.dist_to_actor = self.online and self.position:distance_to(se_actor.position) or math.huge if self.respawn_params ~= nil then self:try_respawn() end if (self.online) then self:update_jobs() -- Unlock relevant article in guide and track statistic. if (self.dist_to_actor <= 50) then SendScriptCallback("actor_on_interaction", "smarts", self, self:name()) end if (nearest_to_actor_smart.id) then if (nearest_to_actor_smart.id == self.id) then nearest_to_actor_smart.dist = self.dist_to_actor elseif (self.dist_to_actor < nearest_to_actor_smart.dist) then nearest_to_actor_smart.id = self.id nearest_to_actor_smart.dist = self.dist_to_actor end else nearest_to_actor_smart.id = self.id nearest_to_actor_smart.dist = self.dist_to_actor end if (self.__campfire_check_time == nil or game.get_game_time():diffSec(self.__campfire_check_time) > 12000) then bind_campfire.turn_off_campfires_by_smart_name(self:name()) self.__campfire_check_time = game.get_game_time() end self:check_smart_faction() --self:check_alarm() end SendScriptCallback("smart_terrain_on_update", self) end function se_smart_terrain:set_alarm(enemy) if (self.smart_alarm_time == nil and self.alarm_start_sound) then local sound_str = xr_logic.pick_section_from_condlist(db.actor, self, self.alarm_start_sound) if (sound_str) then xr_sound.set_sound_play(AC_ID, sound_str) end end self.smart_alarm_time = time_global()+self.alarm_length if (enemy) then if not (self.smart_alarm_position) then self.smart_alarm_position = {} end self.smart_alarm_position[enemy:id()] = enemy:position() end end function se_smart_terrain:check_alarm() if not (self.smart_alarm_time) then return false end if (time_global() > self.smart_alarm_time) then self.smart_alarm_time = nil self.smart_alarm_position = nil if (self.alarm_stop_sound) then local sound_str = xr_logic.pick_section_from_condlist(db.actor, self, self.alarm_stop_sound) if (sound_str) then xr_sound.set_sound_play(AC_ID, sound_str) end end return false end return true end function se_smart_terrain:find_job_by_section(section) if (self.disabled and not self.is_on_actor_level) then return end if (self.stalker_jobs) then for i=1,#self.stalker_jobs do if (self.stalker_jobs[i].section == section) then return self.stalker_jobs[i] end end end if (self.monster_jobs) then for i=1,#self.monster_jobs do if (self.monster_jobs[i].section == section) then return self.monster_jobs[i] end end end if (self.heli_jobs) then for i=1,#self.heli_jobs do if (self.heli_jobs[i].section == section) then return self.heli_jobs[i] end end end end function se_smart_terrain:setup_logic(npc) --utils_data.debug_write(self:name().." setup_logic "..tostring(npc and npc:name())) if not (npc) then printf("%s.setup_logic: NPC is nil",self:name()) return end local npc_info = self.npc_info[npc:id()] if not (npc_info) then printf("%s.setup_logic: npc_info is nil! for id=%s",self:name(),npc:id()) return end if not (npc_info.se_obj) then printf("%s.setup_logic: npc_info.se_obj is nil! for id=%s",self:name(),npc:id()) return end local job = npc_info.job if not (job) then printf("%s.setup_logic: npc_info has no job! for id=%s",self:name(),npc:id()) self:unregister_npc(npc_info.se_obj) self:register_npc(npc_info.se_obj) return end local ltx = job.ltx or self.ltx local ltx_name = job.ini_path or self.ltx_name xr_logic.configure_schemes(npc, ltx, ltx_name, npc_info.stype, job.section, job.prefix_name or self:name()) local sect = xr_logic.determine_section_to_activate(npc, ltx, job.section, db.actor) if utils_data.get_scheme_by_section(job.section) == "nil" then printf("[smart_terrain %s] section=%s, don't use section 'nil'!", self:name(), sect) end --printf("npc=%s ltx=%s sect=%s prefix=%s",npc:name(),ltx_name,sect,job.prefix_name or self:name()) xr_logic.activate_by_section(npc, ltx, sect, job.prefix_name or self:name(), false) end -- called in xr_motivator and bind_monster net_spawn() function setup_gulag_and_logic_on_spawn(obj, st, se_obj, stype, loaded) --utils_data.debug_write(strformat("smart_terrain.setup_gulag_and_logic_on_spawn %s",obj and obj:name())) local sim = alife() -- Expedite arrival and job assignment local smart = se_obj.m_smart_terrain_id and se_obj.m_smart_terrain_id ~= 65535 and sim:object(se_obj.m_smart_terrain_id) if (smart and smart:clsid() == clsid.smart_terrain) then local npc_info = smart.npc_info[se_obj.id] if (npc_info) then if not (npc_info.job) then smart:select_npc_job(npc_info,true) local smart_task = smart.npc_info[se_obj.id].job and smart.npc_info[se_obj.id].job.alife_task if (smart_task) then db.spawned_vertex_by_id[se_obj.id] = smart_task:level_vertex_id() end elseif (npc_info.job) then -- if already job begin then don't spawn at job local smart_task = npc_info.begin_job ~= true and npc_info.job and npc_info.job.alife_task or nil if (smart_task) then db.spawned_vertex_by_id[se_obj.id] = smart_task:level_vertex_id() end npc_info.begin_job = true empty_table(db.offline_objects[se_obj.id]) smart:setup_logic(obj) end return end end xr_logic.initialize_obj(obj, st, loaded, db.actor, stype) end function on_death(se_obj) local sim = alife() local smart = sim and se_obj.m_smart_terrain_id and se_obj.m_smart_terrain_id ~= 65535 and sim:object(se_obj.m_smart_terrain_id) if (smart) then smart:clear_dead(se_obj) 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) if (squad.m_game_vertex_id == self.m_game_vertex_id) then return true end local gg = game_graph() if (gg:vertex(squad.m_game_vertex_id):level_id() ~= gg:vertex(self.m_game_vertex_id):level_id()) then return false end -- TODO: Maybe consider to return true if squad:get_script_target() is true if (is_squad_monster[squad.player_id] and squad:get_script_target() == nil) then return squad.position:distance_to_sqr(self.position) <= 625 end return squad.always_arrived == true or squad.position:distance_to_sqr(self.position) <= self.arrive_dist^2 end function se_smart_terrain:on_after_reach(squad) local sim = alife() for k in squad:squad_members() do local se_obj = k.object or k.id and sim:object(k.id) if (se_obj) then SIMBOARD:setup_squad_and_group(se_obj) end end squad.current_target_id = self.id end function se_smart_terrain:on_reach_target(squad) --utils_data.debug_write(strformat("%s:on_reach_target %s",self:name(),squad and squad:name())) squad:set_location_types(self:name()) SIMBOARD:assign_squad_to_smart(squad, self.id) for k in squad:squad_members() do if db.offline_objects[k.id] ~= nil then empty_table(db.offline_objects[k.id]) end db.spawned_vertex_by_id[k.id] = nil end end -- CALifeSmartTerrainTask 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 id,v in pairs(board_smart_squads) do local squad = alife_object(id) if (squad and squad:get_script_target() == nil) then count = count + 1 end end return count end function se_smart_terrain:sim_available() return true end function surge_stats() end function se_smart_terrain:target_precondition(squad, ignore_population, skip_props) --utils_data.debug_write(strformat("%s:target_precondition",squad and squad:name())) -- commented out because smarts on other levels don't load job table and that means squads won't target them -- if (self.job_count == 0) then -- -- can't target a smart that doesn't have any simulation jobs available -- return false -- end if self.respawn_only_smart == true then return false end -- squad is already stayed here don't count population if not (ignore_population) then local squad_count = SIMBOARD.smarts[self.id].population -- smart_terrain_squad_count(SIMBOARD.smarts[self.id].squads) if (squad_count and squad_count >= self.max_population) then return false end end if not (self.props) then return false end local is_monster = is_squad_monster[squad.player_id] if (is_monster) then if (self.props.all > 0 or self.props.all_monster > 0 or self.props[squad.player_id] > 0) then if (skip_props) and (self.props.lair > 0 or self.props.territory > 0) then return true end -- lair if (self.props.lair > 0 and sim_board.general_lair_precondition(squad,self)) then return true end -- territory if (self.props.territory > 0 and sim_board.general_territory_precondition(squad,self)) then return true end end elseif (squad.player_id == "zombied") then if (self.props.all > 0 or self.props.all_stalker > 0 or self.props[squad.player_id] > 0) then if (skip_props) and (self.props.territory > 0) then return true end -- territory if (self.props.territory > 0 and sim_board.general_territory_precondition(squad,self)) then return true end end else if (self.props.all and self.props.all > 0) or (self.props.all_stalker and self.props.all_stalker > 0) or (self.props[squad.player_id] and self.props[squad.player_id] > 0) then if (skip_props) and (self.props.base > 0 or self.props.resource > 0 or self.props.territory > 0) then return true end -- surge if (squad.player_id ~= "monolith" and xr_conditions.surge_started()) then if (self.props.surge > 0) then return true end return false end -- base if (self.props.base > 0 and sim_board.general_base_precondition(squad,self)) then return true end -- resource if (self.props.resource > 0 and sim_board.general_resource_precondition(squad,self)) then return true end -- territory if (self.props.territory > 0 and sim_board.general_territory_precondition(squad,self)) then return true end end end return false end function se_smart_terrain:evaluate_prior(squad) return simulation_objects.evaluate_prior(self, squad) end local available_sects = {} function se_smart_terrain:try_respawn() local flags = { disabled = false } SendScriptCallback("on_try_respawn",self,flags) if self.disabled or flags.disabled then return end if (has_alife_info("actor_made_wish_for_peace")) then return end if not (self.is_on_actor_level) then if (self.respawn_only_level) then return end end --utils_data.debug_write(strformat("%s:try_respawn",self:name())) if (self.is_on_actor_level and self.dist_to_actor ~= nil) then if (self.dist_to_actor < self.respawn_radius) then return end end if not (self.respawn_params and self.already_spawned) then return end -- simulation_objects.available_by_id[self.id] nil is unprocessed false is unavail if (simulation_objects.available_by_id[self.id] == false) then return end --[[ local squad_count = smart_terrain_squad_count(SIMBOARD.smarts[self.id].squads) if self.max_population <= squad_count then return end --]] local curr_time = game.get_game_time() -- first spawn after game load --[[ if (self.last_respawn_update == nil and (math.random(1,100)/100) < 0.3) then self.last_respawn_update = curr_time return end --]] -- SMR local can_respawn = smr_pop.smart_can_respawn(self) -- SMR END if can_respawn then self.last_respawn_update = curr_time iempty_table(available_sects) local size_t = 0 -- check self.faction if not (self.faction) then self.faction = self.default_faction end local max_respawn_count -- SMR local stalker_pop_factor = smr_pop.get_stalker_pop_factor() local monster_pop_factor = smr_pop.get_monster_pop_factor() -- SMR END for k,v in pairs(self.respawn_params) do if (v.num and self.already_spawned[k] and self.already_spawned[k].num) then if (self.faction_controlled == nil) or (self.faction ~= nil and v.faction == self.faction) then max_respawn_count = tonumber(xr_logic.pick_section_from_condlist(db.actor, self,v.num) or 0) -- Tronex -- Safer check for common squads -- DPH / SMR -- This doesn't work. "k" is the name of the spawn section, not a squad section if (v["squads"]) then -- Just take the first squad section to determine type. Not correct, but accurate in almost all cases local k2 = v["squads"][1] if ini_sys:r_bool_ex(k2,"common") then -- almost all spawn sections with "zombied" in them have mutants as other spawns if (s_find(k2,"simulation") or s_find(k2,"zombied")) then max_respawn_count = round_idp(max_respawn_count*monster_pop_factor) elseif (s_find(k2,"sim_squad")) then max_respawn_count = round_idp(max_respawn_count*stalker_pop_factor) end end end -- DPH / SMR END -- modifier for spawn chances if max_respawn_count < 1 then local respawn_chance = max_respawn_count*100 if (math.random(1,100) > respawn_chance) then max_respawn_count = 0 end end if max_respawn_count > self.already_spawned[k].num then size_t = size_t + 1 available_sects[size_t] = k end end else log("ERROR: %s Incorrect Respawn Params. respawn_params[%s].num=%s already_spawned[%s]=%s already_spawned[%s].num=%s",self:name(),k,v.num,k,type(self.already_spawned[k]),self.already_spawned[k] and self.already_spawned[k].num) end end if size_t > 0 then local sect_to_spawn = available_sects[math.random(1,size_t)] local sect_to_spawn_params = self.respawn_params[sect_to_spawn] if (sect_to_spawn_params.squads) then local squad local random_squad = sect_to_spawn_params.squads[math.random(1,#sect_to_spawn_params.squads)] -- SMR squad = smr_pop.smr_handle_spawn(random_squad, self) -- SMR END if (squad) then squad.respawn_point_id = self.id squad.respawn_point_prop_section = sect_to_spawn for m in squad:squad_members() do SIMBOARD:setup_squad_and_group(m.object) end else -- SMR: just fills up the log needlessly in our case. --printe("!ERROR: call respawn failed for %s, check squad descriptions",sect_to_spawn) return end -- SMR if (squad) then smr_debug.get_log().info("smart", "setting up civil war relations for squad %s (smart: %s)", squad:section_name(), self:name()) smr_civil_war.setup_civil_war_squad(squad, self:name()) end -- SMR END elseif (sect_to_spawn_params.helicopter and ui_options.get("alife/general/heli_spawn")) then local heli = sect_to_spawn_params.helicopter[math.random(1,#sect_to_spawn_params.helicopter)] if (heli) then --if (heli and self.online) then local pos = vector():set(level.get_bounding_volume().max.x,level.get_bounding_volume().min.y-50,level.get_bounding_volume().max.z) local lvid = self.m_level_vertex_id local gvid = self.m_game_vertex_id local se_heli = alife_create(heli,pos,lvid,gvid) local visual = ini_sys:r_string_ex(heli,"visual") -- required to spawn by script local data = utils_stpk.get_heli_data(se_heli) if (data) then data.visual_name = visual and visual ~="" and visual or [[dynamics\vehicles\mi2\veh_mi2_01]] data.motion_name = [[helicopter\aaa.anm]] data.startup_animation = "idle" data.skeleton_name = "idle" data.engine_sound = [[vehicles\helicopter\helicopter]] utils_stpk.set_heli_data(data,se_heli) self:register_npc(se_heli) se_heli.respawn_point_id = self.id se_heli.respawn_point_prop_section = sect_to_spawn else safe_release_manager.release(se_heli) end else return end end self.already_spawned[sect_to_spawn].num = self.already_spawned[sect_to_spawn].num + 1 end end end ------------ local smart_names_table function get_smart_terrain_name(smart) if not (smart_names_table) then smart_names_table = {} local names_ini = ini_file("misc\\smart_names.ltx") for i=0,names_ini:line_count("levels")-1 do temp1, level_name, temp2 = names_ini:r_line("levels", i, "", "") if(names_ini:section_exist(level_name)) then smart_names_table[level_name] = {} for i=0,names_ini:line_count(level_name)-1 do result, smart_name, value = names_ini:r_line(level_name, i, "", "") smart_names_table[level_name][smart_name] = value end end end end local level_name = alife():level_name(game_graph():vertex(smart.m_game_vertex_id):level_id()) local smart_name = smart:name() if(smart_names_table[level_name]~=nil) and (smart_names_table[level_name][smart_name]~=nil) then return game.translate_string(smart_names_table[level_name][smart_name]) end return smart_name end