Divergent/mods/Zone Customization Project/gamedata/scripts/smart_terrain.script

1803 lines
57 KiB
Plaintext
Raw Blame History

------------------------------
-- 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
----------------------------------------------------------------------------------------------------------------------
-- <20><><EFBFBD><EFBFBD><EFBFBD> "se_smart_terrain". <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> smart terrain <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
----------------------------------------------------------------------------------------------------------------------
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
--]]
--*******************************************************
-- <20><><EFBFBD><EFBFBD>/<2F><><EFBFBD><EFBFBD>
--*******************************************************
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")
-- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD>
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
-- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
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")
-- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD>
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
--' <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
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