---==================================================================================================================--- --- --- --- Original Author(s) : GhenTuong --- --- Edited : N/A --- --- Date : 15/07/2022 --- --- License : Proprietary --- --- --- --- Extension script for xr_logic.script. --- --- --- ---==================================================================================================================--- --[[---------------------------------------------------------------------------------------------------- Conditions ------------------------------------------------------------------------------------------------------]] xr_conditions.dist_to_beh = function(actor,npc,p) local st = npc and db.storage[npc:id()] local dt = st and st.beh and st.beh.desired_target if (dt and (st.active_section == dt.active_section) and dt.position) then return p and p[1] and npc:position():distance_to_sqr(dt.position) < p[1]*p[1] end return false end xr_conditions.reach_beh = function(actor,npc,p) local st = npc and db.storage[npc:id()] local dt = st and st.beh and st.beh.desired_target if (dt and (st.active_section == dt.active_section) and dt.level_vertex_id and (dt.level_vertex_id == npc:level_vertex_id())) then return true end return false end xr_conditions.npc_squad_name = function(actor,npc,p) local squad = get_object_squad(npc) if (p and p[1] and squad and string.find(squad:section_name(),p[1])) then return true end return false end xr_conditions.enemy_squad = function(enemy,npc,p) local squad = get_object_squad(enemy) if (p and p[1] and squad and string.find(squad:section_name(),p[1])) then return true end return false end xr_conditions.check_squad_section = function(actor,squad,p) if (p and p[1] and squad and string.find(squad:section_name(),p[1])) then return true end return false end xr_effects.squad_self_release = function(actor,squad,p) return squad and squad.id and alife_release(squad) end xr_conditions.check_time_speech = function(actor,npc,p) local st = db.storage[npc:id()] local dt = st and st.beh and st.beh.desired_target if not (dt and dt.speech_delay_until) then return true end if (dt.speech_delay_until < time_global()) then return true end return false end xr_effects.speech = function(actor,npc,p) local se = npc and (type(npc.id) == "function") and alife_object(npc:id()) se = se or (npc and (type(npc.id) == "number") and alife_object(npc.id)) local name = se and se:character_name() local text = p and p[1] and game.translate_string(p[1]) if (name and text) then dynamic_news_helper.send_tip(text,name,nil,15,"ui_npc_duty_girl_msg_image",nil,"npc") if (tonumber(p[2])) then local st = db.storage[npc:id()] local dt = st and st.beh and st.beh.desired_target if (dt) then dt.speech_delay_until = time_global() + tonumber(p[2]) * 1000 end end end end function get_waypoint(npc,section,pt) local st = npc and db.storage[npc:id()] if (st and st.ini and (section or st.active_section) and pt and tostring(pt)) then local str = st.ini:r_string_ex(section or st.active_section,tostring(pt)) if (str) then for s in string.gmatch(str,"pos:(%A+)") do local v = str_explode(s,",") if (tonumber(v[1]) and tonumber(v[2]) and tonumber(v[3])) then return vector():set(tonumber(v[1]),tonumber(v[2]),tonumber(v[3])) end end end end end function set_position(actor,npc,p) local pos = p and p[1] and p[2] and get_waypoint(npc,p[1],p[2]) if (pos) then npc:set_npc_position(pos) end end --[[---------------------------------------------------------------------------------------------------- Stalker state ------------------------------------------------------------------------------------------------------]] function state_idle(actor,npc,p) local new_state = p and p[1] local state = npc and state_mgr.get_state(npc) if (new_state and state and (new_state ~= state)) then state_mgr.set_state(npc,new_state) end end --[[---------------------------------------------------------------------------------------------------- Movement ------------------------------------------------------------------------------------------------------]] local function validate(npc,vid) if (vid and vid < 4294967295 and npc:accessible(vid) and vid ~= npc:level_vertex_id()) then return db.used_level_vertex_ids[vid] == nil or db.used_level_vertex_ids[vid] == npc:id() end return false end local function lmove(npc,vid,st) if (vid == nil or vid >= 4294967295) then return end if (db.used_level_vertex_ids[vid] == npc:id()) then npc:set_dest_level_vertex_id(vid) return vid end if (st.vid) then db.used_level_vertex_ids[st.vid] = nil end if not (npc:accessible(vid)) then local vtemp = VEC_ZERO vid, vtemp = npc:accessible_nearest(vid and level.vertex_position(vid) or npc:position(), vtemp ) end db.used_level_vertex_ids[vid] = npc:id() st.vid = vid npc:set_dest_level_vertex_id(vid) return vid end --[[---------------------------------------------------------------------------------------------------- Animpoints ------------------------------------------------------------------------------------------------------]] local campfire_state_tbl = { idle = {director = {"", "_eat_bread", "_eat_kolbasa", "_drink_vodka", "_drink_energy", "_weapon", "_roast_kolbasa", "_roast_kolbasa_bred", "_roast_bred", "_sleep", "_use_pda"}, listener = {"", "_eat_bread", "_eat_kolbasa", "_drink_vodka", "_drink_energy", "_weapon", "_roast_kolbasa", "_roast_kolbasa_bred", "_roast_bred", "_sleep", "_use_pda"}}, harmonica = {director = {"_harmonica"}, listener = {"", "_eat_bread", "_eat_kolbasa", "_drink_vodka", "_drink_energy", "_weapon", "_roast_kolbasa", "_roast_kolbasa_bred", "_roast_bred", "_sleep", "_use_pda"}}, guitar = {director = {"_guitar"}, listener = {"", "_eat_bread", "_eat_kolbasa", "_drink_vodka", "_drink_energy", "_weapon", "_roast_kolbasa", "_roast_kolbasa_bred", "_roast_bred", "_sleep", "_use_pda"}}, story = {director = {"", "_weapon"}, listener = {"", "_eat_bread", "_eat_kolbasa", "_drink_vodka", "_drink_energy", "_weapon", "_roast_kolbasa", "_roast_kolbasa_bred", "_roast_bred", "_sleep", "_use_pda"}}, } local state_sit_all = {"animpoint_sit","animpoint_sit_ass","animpoint_sit_knee"} function xr_effects.animpoint(actor,npc) local st = npc and db.storage[npc:id()] local beh = st and st.beh if not (beh) then return end local dt = beh.desired_target if not (dt and (st.active_section == dt.active_section) and (dt.custom_logic == "animpoint")) then local index = next_pt_index(npc) dt = load_animpoint(npc,index) end if (dt and dt.wait_delay and (dt.wait_delay < time_global())) then local index = next_pt_index(npc) dt = load_animpoint(npc,index) end if not (dt.level_vertex_id) then printf("GhenTuong: animpoint %s | %s",st.active_section,dt.level_vertex_id) return end if (st.beh.use_camp and dt.animpoint) then -- Move to camp if (npc:level_vertex_id() ~= dt.level_vertex_id) then npc:set_path_type(game_object.level_path) npc:set_desired_direction() beh.assist_point = utils_obj.send_to_nearest_accessible_vertex(npc,dt.level_vertex_id,"xr_custom.animpoint") set_moving_state(npc) return end -- Get camp if not (beh.camp) then beh.camp = sr_camp.get_current_camp(dt.position) end if (beh.camp and not beh.in_camp) then beh.camp:register_npc(npc:id()) beh.in_camp = true end if not (dt.approved_actions and (#dt.approved_actions > 0)) then local is_in_camp = beh.camp ~= nil local p = {"animpoint_sit_ass","animpoint_sit_knee","animpoint_sit"} dt.description_name = p[math.random(#p)] dt.avail_actions = xr_animpoint_predicates.associations[dt.description_name] if (dt.avail_actions) then local lst = {} for k,v in pairs(dt.avail_actions) do if (v.predicate(npc:id(),is_in_camp) == true) then lst[#lst+1] = v end end dt.approved_actions = lst for k,v in pairs(dt.approved_actions) do printf("GhenTuong: load_campfire_actions [%s] %s is_in_camp %s %s",npc:name(),dt.active_section,is_in_camp,v and v.name) end else return end end local tmp_actions = {} local num = 0 local camp_action, is_director = beh.camp and beh.camp:get_camp_action(npc:id()) if not (camp_action) then printf("GhenTuong: [%s] %s no camp",npc:name(),dt.active_section) return end local tbl = nil if (is_director) then tbl = campfire_state_tbl[camp_action].director dt.__dtimer = time_global() else dt.__dtimer = (not dt.__dtimer and time_global() + math.random(10000,15000)) or dt.__dtimer if (dt.current_action and time_global() < dt.__dtimer) then return end tbl = campfire_state_tbl[camp_action].listener end local found = false for k,v in pairs(dt.approved_actions) do for i = 1, #tbl do if (dt.description_name .. tbl[i] == v.name) then num = num + 1 tmp_actions[num] = v.name found = true end end end if not (found) then num = num + 1 tmp_actions[num] = dt.description_name end dt.current_action = tmp_actions[math.random(num)] for k,v in pairs(tmp_actions) do printf("GhenTuong: animpoint load campfire action %s %s",dt.active_section,v) end local cur_state = state_mgr.get_state(npc) local new_state = dt.current_action if (cur_state and new_state and (cur_state ~= new_state)) then local anim_pos = dt.animpoint and vector():set(dt.animpoint) local anim_dir = dt.direction and vector():set(dt.direction) state_mgr.set_state(npc,new_state,nil,nil,nil,{animation_position = anim_pos, animation_direction = anim_dir}) end return end if (dt.animpoint) then local cur_state = state_mgr.get_state(npc) local new_state = nil if (dt.delay_animation and string.find(dt.delay_animation,"@random")) then if (dt.random_animation == nil) then local str = str_explode(dt.delay_animation,"@") local state_base = tostring(str[1]) state_base = (string.find(state_base,"animpoint_sit_all") and state_sit_all[math.random(#state_sit_all)]) or state_base local p = {"guitar","harmonica","roast","weapon"} local function check_state_exception(state_con,state) for k,v in pairs(p) do if (string.find(state,v) and not string.find(state_con,v)) then return false end end return true end local state_list = {} local t = xr_animpoint_predicates.associations[state_base] if (t) then for k,v in pairs(t) do if (v.name and check_state_exception(dt.delay_animation,tostring(v.name))) then state_list[#state_list+1] = tostring(v.name) end end end if (#state_list == 0) then printf("GhenTuong: xr_logic_ex can not find %s %s",dt.active_section,dt.delay_animation) return end dt.random_animation = state_list[math.random(#state_list)] end new_state = dt.random_animation else new_state = dt.delay_animation end if not (cur_state and new_state) then return end if ((cur_state == new_state) and (npc:position():distance_to_sqr(dt.animpoint) < 0.8)) then if (dt.delay) then if not (dt.wait_delay) then dt.wait_delay = time_global() + dt.delay end end if (dt.sound_idle) then xr_sound.set_sound_play(npc:id(),dt.sound_idle) end return end if (npc:level_vertex_id() ~= dt.level_vertex_id) then npc:set_path_type(game_object.level_path) npc:set_desired_direction() beh.assist_point = utils_obj.send_to_nearest_accessible_vertex(npc,dt.level_vertex_id,"xr_custom.animpoint") set_moving_state(npc) return end if (cur_state == new_state) then if (dt.delay) then if not (dt.wait_delay) then dt.wait_delay = time_global() + dt.delay end end if (dt.sound_idle) then xr_sound.set_sound_play(npc:id(),dt.sound_idle) end return end local reach_state = "animpoint_reach_ex" if (cur_state ~= new_state) and (cur_state ~= reach_state) then local anim_pos = dt.animpoint and vector():set(dt.animpoint) local anim_dir = dt.direction and vector():set(dt.direction) state_mgr.set_state(npc,reach_state,{turn_end_func = function() return end},nil,{look_dir = anim_dir},{animation_position = anim_pos}) end if (cur_state == reach_state) and not (st.callback and st.callback.turn_end_func) then local anim_pos = dt.animpoint and vector():set(dt.animpoint) local anim_dir = dt.direction and vector():set(dt.direction) --local look_pos = dt.look_position and vector():set(dt.look_position) state_mgr.set_state(npc,new_state,nil,nil,nil,{animation_position = anim_pos, animation_direction = anim_dir}) end return end if (npc:level_vertex_id() ~= dt.level_vertex_id) then npc:set_path_type(game_object.level_path) npc:set_desired_direction() beh.assist_point = utils_obj.send_to_nearest_accessible_vertex(npc,dt.level_vertex_id,"xr_custom.animpoint") set_moving_state(npc) return end local cur_state = state_mgr.get_state(npc) local new_state = dt.delay_animation if not (cur_state and new_state) then return end if (cur_state == new_state) then if (dt.delay) then if not (dt.wait_delay) then dt.wait_delay = time_global() + dt.delay end end if (dt.sound_idle) then xr_sound.set_sound_play(npc:id(),dt.sound_idle) end return end local look_pos = dt.look_position and vector():set(dt.look_position) state_mgr.set_state(npc,new_state,nil,nil,{look_position = look_pos}) end function load_animpoint(npc,index) local st = npc and db.storage[npc:id()] local str = st and st.beh and st.ini and st.active_section and st.ini:r_string_ex(st.active_section,"pt" .. index) if not (str) then return {} end local dt = {} dt.active_section = tostring(st.active_section) dt.custom_logic = "animpoint" dt.path_index = tonumber(index) for s in string.gmatch(str,"pos:(%A+)") do local p = str_explode(s,",") dt.position = vector():set(tonumber(p[1]),tonumber(p[2]),tonumber(p[3])) dt.level_vertex_id = dt.position and level.vertex_id(vector():set(dt.position.x,dt.position.y+0.5,dt.position.z)) end if (string.find(str,"animpoint:pos")) then dt.animpoint = dt.position and vector():set(dt.position) else for s in string.gmatch(str,"animpoint:(%A+)") do local p = str_explode(s,",") dt.animpoint = vector():set(tonumber(p[1]),tonumber(p[2]),tonumber(p[3])) end end for s in string.gmatch(str,"dir:(%A+)") do local p = str_explode(s,",") dt.direction = tonumber(p[1]) and vector_rotate_y(vector():set(0,0,1),tonumber(p[1])):normalize() end local pos = dt.animpoint and vector():set(dt.animpoint) or dt.position and vector():set(dt.position) local dir = dt.direction and vector():set(dt.direction) if (pos and dir) then dt.look_position = vector():set(pos.x + 10*dir.x, pos.y, pos.z + 10*dir.z) end if not (dt.direction and dt.look_position) then for s in string.gmatch(str,"look:(%A+)") do local p = str_explode(s,",") dt.look_position = vector():set(tonumber(p[1]),tonumber(p[2]),tonumber(p[3])) end if (pos and dt.look_position) then dt.direction = vector():set(dt.look_position):sub(pos):normalize() end end local pt = str_explode(str,"|") pt = pt[1] and str_explode(pt[1],",") if (pt) then if (pt[1] and (pt[1] ~= "") and (pt[1] ~= "nil")) then dt.delay = tonumber(pt[1]) end if (pt[2] and (pt[2] ~= "") and (pt[2] ~= "nil")) then dt.delay_animation = tostring(pt[2]) end if (pt[3] and (pt[3] ~= "") and (pt[3] ~= "nil")) then dt.sound_idle = tostring(pt[3]) end end st.beh.desired_target = dt return st.beh.desired_target end function next_pt_index(npc) local st = npc and db.storage[npc:id()] local dt = st and st.beh and st.beh.desired_target local index = (dt and (st.active_section == dt.active_section) and tonumber(dt.path_index) or 0) + 1 if (st.ini and st.active_section and st.ini:r_string_ex(st.active_section,"pt" .. index)) then return index end return 1 end function set_moving_state(npc) local beh = db.storage[npc:id()] and db.storage[npc:id()].beh if not (beh) then return end local t = time_global() if (beh.keep_state_until and t < beh.keep_state_until) then return end beh.keep_state_until = t + 500 local new_state = beh.run_animation local dist_w = tonumber(xr_logic.pick_section_from_condlist(db.actor, npc, beh.walk_dist) or 5) or 5 local dist_j = tonumber(xr_logic.pick_section_from_condlist(db.actor, npc, beh.jog_dist) or 10) or 10 local pos = vector():set(npc:position()) local d = beh.desired_target and beh.desired_target.position and pos:distance_to_sqr(beh.desired_target.position) if (beh.assist_point == nil or beh.assist_point == npc:level_vertex_id()) then new_state = beh.wait_animation elseif ((dist_w ~= 0) and d and (d < dist_w*dist_w)) then new_state = beh.walk_animation elseif ((dist_j ~= 0) and d and (d > dist_j*dist_j)) then new_state = beh.jog_animation end state_mgr.set_state(npc,new_state,nil,nil,nil,{fast_set = true,animation = true}) end --[[---------------------------------------------------------------------------------------------------- Campfire ------------------------------------------------------------------------------------------------------]] local storage_camp = {} function generate_campfire_point(smart_name,campfire_name) local function itr(obj) local cf = {} local pos = obj:position() local dir = obj:direction() if (pos and dir) then end if (storage_camp[smart_name] == nil) then storage_camp[smart_name] = {} end storage_camp[smart_name][campfire_name] = cf end if (db.campfire_table_by_smart_names[smart_name]) then for _,k in pairs(db.campfire_table_by_smart_names[smart_name]) do if (k.object and (k.object:name() == campfire_name)) then itr(camp) break end end end end function xr_effects.beh_campfire(actor,npc) local st = npc and db.storage[npc:id()] if not (st and st.beh) then return end local dt = st and st.beh and st.beh.desired_target if not (dt and (st.active_section == dt.active_section) and (dt.custom_logic == "beh_campfire")) then dt = load_beh_campfire(npc,index) end if not (dt and dt.smart_name and dt.campfire_name) then return end end function load_beh_campfire(npc,st) local ini = st.ini local section = st.active_scheme if not (ini and section and ini:section_exist(section)) then return end local dt = {} dt.active_section = tostring(st.active_section) dt.custom_logic = "beh_campfire" dt.smart_name = ini:r_string_ex(section,"smart") dt.campfire_name = ini:r_string_ex(section,"campfire") st.beh.desired_target = dt return st.beh.desired_target end --[[---------------------------------------------------------------------------------------------------- Mutants ------------------------------------------------------------------------------------------------------]] local mutant_state_move = { ["walk"] = move.walk_fwd, ["run"] = move.run_fwd, ["steal"] = move.steal, } local mutant_state_wait = { ["stand"] = anim.stand_idle, ["lie"] = anim.lie_idle, ["sleep"] = anim.sleep, } function xr_effects.mutant_path(actor,npc,p) local st = npc and db.storage[npc:id()] if not (st and st.beh) then return end local dt = st and st.beh and st.beh.desired_target if not (dt and (st.active_section == dt.active_section) and (dt.custom_logic == "mutant_path")) then dt = mutant_load_path(npc) end if not (dt.level_vertex_id and dt.position) then return end if (npc:get_enemy()) then if (npc:clsid() == clsid.bloodsucker_s) then npc:release_stand_sleep_animation() end return end xr_logic.mob_capture(npc,true) local t = time_global() if (dt.keep_state_until and t < dt.keep_state_until) then return end dt.keep_state_until = t + 1000 --local squad = get_object_squad(npc) --printf("GhenTuong: mutant_path current_action = %s | assigned_target_id = %s",squad.current_action,squad.assigned_target_id) if (npc:level_vertex_id() ~= dt.level_vertex_id) then local state = xr_logic.pick_section_from_condlist(db.actor,npc,dt.move_animation) local new_state = (state and mutant_state_move[state]) or mutant_state_move["walk"] action(npc,move(new_state,dt.level_vertex_id,dt.position),cond(cond.move_end)) if (npc:clsid() == clsid.bloodsucker_s) then npc:release_stand_sleep_animation() end --printf("GhenTuong: mutant_path %s %s",npc:name(),dt.position:distance_to_sqr(npc:position())) return end if (dt.look_position) then local state = xr_logic.pick_section_from_condlist(db.actor,npc,dt.wait_animation) if (state == "bloodsucker_sleep") then local rot_y = vector_angle_diff(npc:direction(),dt.direction) if (rot_y and rot_y < 10) then if not (dt.force_stand_sleep_animation_index) then dt.force_stand_sleep_animation_index = math.random(0,1) end npc:force_stand_sleep_animation(dt.force_stand_sleep_animation_index) return end action(npc,anim(mutant_state_wait["stand"],0),look(look.point,dt.look_position),cond(cond.look_end)) return end if (npc:clsid() == clsid.bloodsucker_s) then npc:release_stand_sleep_animation() end local new_state = (state and mutant_state_wait[state]) or mutant_state_wait["stand"] action(npc,anim(new_state,0),look(look.point,dt.look_position),cond(cond.look_end)) end end function mutant_load_path(npc) local st = npc and db.storage[npc:id()] local str = st and st.beh and st.ini and st.active_section and st.ini:r_string_ex(st.active_section,"pt1") if not (str) then return {} end local dt = {} dt.active_section = tostring(st.active_section) dt.custom_logic = "mutant_path" dt.move_animation = st.ini:r_string_to_condlist(st.active_section,"move_animation","walk") dt.wait_animation = st.ini:r_string_to_condlist(st.active_section,"wait_animation","stand") for s in string.gmatch(str,"smart:(%S+)") do local smart = SIMBOARD.smarts_by_names[s] dt.position = smart and vector():set(smart.position) dt.level_vertex_id = smart and tonumber(smart.m_level_vertex_id) end for s in string.gmatch(str,"sound:(%S+)") do dt.sound = tostring(s) end for s in string.gmatch(str,"pos:(%A+)") do local p = str_explode(s,",") if (tonumber(p[1]) and tonumber(p[2]) and tonumber(p[3])) then dt.level_vertex_id = level.vertex_id(vector():set(tonumber(p[1]),tonumber(p[2])+0.5,tonumber(p[3]))) dt.position = dt.level_vertex_id and vector():set(level.vertex_position(dt.level_vertex_id)) end end for s in string.gmatch(str,"dir:(%A+)") do local p = str_explode(s,",") if (tonumber(p[1]) and tonumber(p[2]) and tonumber(p[3])) then dt.direction = vector():set(tonumber(p[1]),tonumber(p[2]),tonumber(p[3])):normalize() elseif (tonumber(p[1])) then dt.direction = vector_rotate_y(vector():set(0,0,1),tonumber(p[1])):normalize() end local pos = vector():set(dt.position) local dir = vector():set(dt.direction) dt.look_position = pos and dir and vector():set(pos.x + 10*dir.x, pos.y, pos.z + 10*dir.z) end if not (dt.position) then printf("GhenTuong: %s [%s] no dt.position",st.ini_filename,st.active_section) end if not (dt.level_vertex_id) then printf("GhenTuong: %s [%s] no dt.level_vertex_id",st.ini_filename,st.active_section) end if not (dt.look_position) then printf("GhenTuong: %s [%s] no dt.look_position",st.ini_filename,st.active_section) end --[[ printf("GhenTuong: mutant_load_path %s ", dt.level_vertex_id) printf("GhenTuong: mutant_load_path %s ", dt.position) printf("GhenTuong: mutant_load_path %s ", dt.look_position) --]] st.beh.desired_target = dt return st.beh.desired_target end function vector_angle_diff(dir1,dir2) if (dir1 and dir2) then local v1 = -math.deg(math.atan2(dir1.x,dir1.z)) local v2 = -math.deg(math.atan2(dir2.x,dir2.z)) return math.abs(math.min(math.abs(v1-v2),360-math.abs(v1)-math.abs(v2))) end end --[[---------------------------------------------------------------------------------------------------- Main ------------------------------------------------------------------------------------------------------]] local storage_set_position = {} local storage_logic = {} local storage_squad = {} function squad_on_update(squad) if not (squad and ini_sys:line_exist(squad:section_name(),"logic")) then return end if (axr_companions.companion_squads and axr_companions.companion_squads[squad.id]) then return end local ini_name = ini_sys:r_string_ex(squad:section_name(),"logic") if not (storage_squad[ini_name] or load_storage_squad(ini_name)) then return end squad_target_update(squad,storage_squad[ini_name]) if not (storage_logic[ini_name] or load_storage_logic(ini_name)) then return end --printf("GhenTuong: squad_on_update %s",squad:section_name()) for k in squad:squad_members() do local st = k and db.storage[k.id] if (st) then local npc = st.object or level.object_by_id(k.id) if (npc and npc:alive()) then npc_logic_update(npc,st,squad,ini_name,storage_logic[ini_name]) end end end end function load_storage_squad(ini_name) local ini = ini_file(ini_name) if not (ini) then printf("GhenTuong: xr_logic_ex | load_storage_squad file %s doesn't exist.",ini_name) return false end local tbl = {} if (ini:section_exist("section@squad")) then local n = ini:line_count("section@squad") local t = 0 for k=0,n-1 do local r,i,v = ini:r_line("section@squad",k) if (i and v) then if (string.find(i,"target")) then t = t + 1 tbl[t] = {} tbl[t].condlist = ini:r_string_to_condlist("section@squad",i) tbl[t].teleport = string.find(i,"@") and true or false --printf("GhenTuong: xr_logic_ex load_storage_squad | %s = %s",i,v) end end end tbl.target_num = t if (ini:line_exist("section@squad","condlist")) then tbl.condlist = ini:r_string_to_condlist("section@squad","condlist") end end storage_squad[ini_name] = tbl return true end function load_storage_logic(ini_name) local ini = ini_file(ini_name) if not (ini) then printf("GhenTuong: xr_logic_ex | load_storage_logic file %s doesn't exist.",ini_name) return false end local tbl = {} if (ini:section_exist("section@logic")) then local n = ini:line_count("section@logic") for k=0,n-1 do local result,i,v = ini:r_line("section@logic",k) if (i and (i ~= "") and (i ~= "nil") and ini:section_exist(i) and string.find(i,"logic")) then tbl[i] = {} tbl[i].prior = ini:r_float_ex(i,"prior") or 0 tbl[i].logic = ini:r_string_to_condlist(i,"suitable","true") --printf("GhenTuong: xr_logic_ex load_storage_logic | section@logic %s ",i) end end end storage_logic[ini_name] = tbl return true end function squad_target_update(squad,tbl) if (tbl.condlist) then xr_logic.pick_section_from_condlist(db.actor,squad,tbl.condlist) end local new_target = nil local offline_teleport = false if (tbl.target_num) then for i=1,tbl.target_num,1 do local k = tbl[i] local target = k and k.condlist and xr_logic.pick_section_from_condlist(db.actor,squad,k.condlist) if (target and (target ~= "") and (target ~= "nil")) then new_target = target offline_teleport = k.teleport and true break end end end if (new_target) then local obj = nil if (false) then elseif (new_target == "self") then if (squad.scripted_target ~= squad.id) then squad.scripted_target = tonumber(squad.id) end obj = squad elseif (SIMBOARD.smarts_by_names[new_target]) then if (squad.scripted_target ~= new_target) then squad.scripted_target = new_target end obj = SIMBOARD.smarts_by_names[new_target] else local se_obj = get_story_se_object(new_target) if (se_obj and se_obj.id) then if (squad.scripted_target ~= se_obj.id) then squad.scripted_target = tonumber(se_obj.id) end obj = se_obj end end if (offline_teleport and (squad.online ~= true)) then if (obj and not simulation_objects.is_on_the_same_level(squad,obj)) then local pos = obj.position local vid = obj.m_level_vertex_id local gid = obj.m_game_vertex_id if (pos and vid and gid) then TeleportSquad(squad,pos,vid,gid) printf("GhenTuong: squad_target_update teleport [%s] %s %s",squad:name(),new_target,squad.scripted_target) end end end --printf("GhenTuong: xr_logic_ex | squad_target_update [%s] %s %s",squad:name(),new_target,squad.scripted_target) end end function npc_logic_update(npc,st,squad,ini_name,tbl) if (npc:has_info("npcx_is_companion")) then --printf("GhenTuong: npc_logic_update squad isn't companion but npc is? [%s]",npc:name()) return end local npc_id = npc:id() -- Keep using current logic if it is still valid. local using = st.section_logic and tbl[st.section_logic] local check = using and (xr_logic.pick_section_from_condlist(db.actor,npc,using.logic) == "true") -- Choose new logic local new_logic = check and tostring(st.section_logic) or "" local new_prior = check and tonumber(using.prior) or -1 -- Case of save/load if not (using) then for i,v in pairs(tbl) do if (v.own_id == npc_id) then check = xr_logic.pick_section_from_condlist(db.actor,npc,v.logic) == "true" new_logic = check and tostring(i) or "" new_prior = check and tonumber(v.prior) or -1 --printf("GhenTuong: xr_logic_ex | re-use [%s] %s %s",npc:name(),new_logic,new_prior) break end end end for i,v in pairs(tbl) do if ((v.prior > new_prior) and (xr_logic.pick_section_from_condlist(db.actor,npc,v.logic) == "true")) then local k = v.own_id and (v.own_id ~= npc_id) and db.storage[v.own_id] if not (k and k.object and k.object:alive() and k.section_logic and (k.section_logic == i)) then new_logic = tostring(i) new_prior = tonumber(v.prior) end end end --printf("GhenTuong: xr_logic_ex | npc_logic_update [%s] %s %s",npc:name(),st.section_logic,st.active_section) if (new_logic and (new_logic ~= st.section_logic) and (new_logic ~= "") and (new_logic ~= "nil") and tbl[new_logic]) then printf("GhenTuong: xr_logic_ex | old logic [%s] %s %s",npc:name(),st.section_logic,st.active_section) tbl[new_logic].own_id = npc_id npc_switch_new_logic(npc,ini_name,new_logic) end end function npc_switch_new_logic(npc,ini_name,new_logic) local ini = ini_file(ini_name) local cls = npc.clsid and npc:clsid() local sty = (IsStalker(nil,cls) and 0) or (IsMonster(nil,cls) and 1) or nil --Active scheme section xr_logic.configure_schemes(npc,ini,ini_name,sty,new_logic,"") local new_section = xr_logic.determine_section_to_activate(npc,ini,new_logic,db.actor) xr_logic.activate_by_section(npc,ini,new_section,"",false) printf("GhenTuong: xr_logic_ex | new logic [%s] %s %s %s",npc:name(),ini_name,new_logic,new_section) end function npc_on_net(npc,se_obj) if not (npc:alive()) then return end local squad = get_object_squad(npc) if (squad and axr_companions.companion_squads and axr_companions.companion_squads[squad.id]) then return end if (npc:has_info("npcx_is_companion")) then --printf("GhenTuong: npc_on_net squad isn't companion but npc is? [%s]",npc:name()) return end if (squad and ini_sys:line_exist(squad:section_name(),"logic")) then local ini_name = ini_sys:r_string_ex(squad:section_name(),"logic") if not (storage_squad[ini_name] or load_storage_squad(ini_name)) then return end squad_target_update(squad,storage_squad[ini_name]) if not (storage_logic[ini_name] or load_storage_logic(ini_name)) then return end --Force squad to update target so don't fuck up my scheme setting for npc. local script_target_id = squad:get_script_target() if (script_target_id) then squad:specific_update(script_target_id) else squad:generic_update() end local st = db.storage[npc:id()] if (st) then npc_logic_update(npc,st,squad,ini_name,storage_logic[ini_name]) end end -- For smart exclusive logic too. Not only my custom squad logic. npc_set_position(npc,se_obj,squad) end function npc_set_position(npc,se_obj,squad) local id = se_obj.id local st = db.storage[id] local ini = st.ini local section_logic = st.section_logic local active_section = st.active_section if not (ini and section_logic and active_section and ini:line_exist(section_logic,"net_spawn")) then return end if (db.spawned_vertex_by_id[id]) then db.spawned_vertex_by_id[id] = nil end if (db.offline_objects[id] and db.offline_objects[id].level_vertex_id) then db.offline_objects[id].level_vertex_id = nil end local target = xr_logic.pick_section_from_condlist(db.actor,npc,ini:r_string_to_condlist(section_logic,"net_spawn","nil")) if not (target and (target ~= "") and (target ~= "nil")) then return end local pos = nil if (false) then elseif (target == "actor") then pos = db.actor:position() elseif (SIMBOARD.smarts_by_names[target]) then pos = vector():set(SIMBOARD.smarts_by_names[target].position) elseif (ini:line_exist(active_section,target)) then local str = ini:r_string(active_section,target) if (string.find(str,"pos:")) then for s in string.gmatch(str,"pos:(%A+)") do local p = str_explode(s,",") if (tonumber(p[1]) and tonumber(p[1]) and tonumber(p[1])) then pos = vector():set(tonumber(p[1]),tonumber(p[2]),tonumber(p[3])) end end end end if (storage_set_position[npc:name()]) then --printf("GhenTuong: npc_set_position skip [%s]",npc:name()) return end storage_set_position[npc:name()] = true npc:set_npc_position(pos) --printf("GhenTuong: npc_set_position [%s] %s",npc:name(),pos) end function ignore_smart_job(npc) local story_id = npc and (type(npc.id) == "number") and get_story_object_id(npc.id) if (story_id and (string.find(story_id,"esc_2_12_stalker_trader") or string.find(story_id,"red_forester_tech"))) then return false end local squad = npc and get_object_squad(npc) if (squad and ini_sys:line_exist(squad:section_name(),"logic")) then return true end return false end function monster_on_update(npc,st) if not (st and st.ini and st.active_section and st.ini:line_exist(st.active_section,"target")) then return end if not (db.actor and npc and npc:alive()) then return end if not (st.beh and st.beh.target and (st.beh.active_section == st.active_section)) then st.beh = {} st.beh.active_section = tostring(st.active_section) st.beh.target = st.ini:r_string_to_condlist(st.active_section,"target","nil") end xr_logic.pick_section_from_condlist(db.actor,npc,st.beh.target) end function npc_on_before_hit(npc,shit,bone_id,flags) local st = npc and db.storage[npc:id()] if not (st and st.ini and st.active_section and st.ini:line_exist(st.active_section,"before_hit")) then return end local str = st.ini:r_string_ex(st.active_section,"before_hit") if (str) then if string.find(str,"invulnerable") then if (tonumber(npc.health) < 1) then npc:set_health_ex(1) end flags.ret_value = false return end if string.find(str,"@") then local p = str_explode(str,"@") if (p and #p == 2) then local v = _G[tostring(p[1])][tostring(p[2])](npc,shit,bone_id,flags) if (tostring(v) and string.find(tostring(v),"invulnerable")) then if (tonumber(npc.health) < 1) then npc:set_health_ex(1) end flags.ret_value = false end end end end end function npc_on_eval_danger(npc,flags) local st = npc and db.storage[npc:id()] if not (st and (st.active_scheme == "beh")) then return end local con = st.ini and st.active_section and st.ini:r_string_to_condlist(st.active_section,"danger_ignore") if (con) then if (xr_logic.pick_section_from_condlist(db.actor,npc,con) == "true") then flags.ret_value = false return end end end local storage_smart = {} function smart_terrain_on_update(smart) if not (smart and smart.ini and smart.ini:section_exist("on_changing_level")) then return end if (storage_smart[smart:name()]) then return end storage_smart[smart:name()] = true local n = smart.ini:line_count("on_changing_level") for k=0,n-1 do local r,i,v = smart.ini:r_line("on_changing_level",k) if (i and v and string.find(i,"on_info")) then --printf("smart_terrain_on_update %s",smart:name()) local con = smart.ini:r_string_to_condlist("on_changing_level",i,"nil") xr_logic.pick_section_from_condlist(db.actor,smart,con) end end end function save_state(m_data) m_data.xr_logic_ex_storage_set_position = storage_set_position m_data.xr_logic_ex_storage_logic = storage_logic m_data.xr_logic_ex_storage_smart = storage_smart end function load_state(m_data) storage_set_position = m_data.xr_logic_ex_storage_set_position or {} storage_logic = m_data.xr_logic_ex_storage_logic or {} storage_smart = m_data.xr_logic_ex_storage_smart or {} end function on_level_changing() storage_set_position = {} storage_logic = {} storage_smart = {} end --[[---------------------------------------------------------------------------------------------------- Registers ------------------------------------------------------------------------------------------------------]] function on_game_start() RegisterScriptCallback("save_state",save_state) RegisterScriptCallback("load_state",load_state) RegisterScriptCallback("squad_on_update",squad_on_update) RegisterScriptCallback("on_level_changing",on_level_changing) RegisterScriptCallback("monster_on_update",monster_on_update) RegisterScriptCallback("npc_on_net_spawn",npc_on_net) RegisterScriptCallback("monster_on_net_spawn",npc_on_net) RegisterScriptCallback("npc_on_before_hit",npc_on_before_hit) RegisterScriptCallback("monster_on_before_hit",npc_on_before_hit) RegisterScriptCallback("npc_on_eval_danger",npc_on_eval_danger) RegisterScriptCallback("smart_terrain_on_update",smart_terrain_on_update) end --[[---------------------------------------------------------------------------------------------------- States ------------------------------------------------------------------------------------------------------]] function add_states() return { animpoint_reach_ex = { weapon = "strapped", movement = nil, mental = nil, bodystate = nil, animstate = nil, animation = nil, direction = CSightParams.eSightTypeAnimationDirection } } end copy_table(state_lib.states, add_states()) --[[---------------------------------------------------------------------------------------------------- Overrides ------------------------------------------------------------------------------------------------------]] function xr_combat_ignore.ignore_enemy_by_overrides(obj,enemy,no_check_job) if not (enemy) then return true end if (IsStalker(obj)) then if (enemy:section() == "mar_smart_terrain_doc_dog" or enemy:section() == "mar_smart_terrain_base_dog_doctor") then return true end end local id = obj:id() local ene_id = enemy:id() local st = db.storage[id] and db.storage[id].overrides -- This skips enemy_ignore of obj when enemy doesn't have overrides, considered a bug. -- Ex: enemy that doesn't have logic at all, enemy squad on moving to a smart terrain. --if not (st) then -- return false --end -- combat_ignore_cond from custom data logic local ignore = st and st.combat_ignore and xr_logic.pick_section_from_condlist(enemy, obj, st.combat_ignore.condlist) if (ignore == "true") then --obj:enable_memory_object(enemy,false) return true end -- enemy_ignore_cond override from custom data logic -- if this is true then npc will IGNORE combat with this specific enemy local ene_st = db.storage[ene_id] and db.storage[ene_id].overrides if (ene_st) then ignore = ene_st.enemy_ignore and xr_logic.pick_section_from_condlist(enemy, obj, ene_st.enemy_ignore.condlist) if (ignore == "true") then --obj:enable_memory_object(enemy,false) return true end end -- Ignore enemies because of no_combat_job if (no_check_job ~= true) and (st and st.no_combat_job and xr_logic.pick_section_from_condlist(enemy, obj, st.no_combat_job.condlist) == "true") then return true end return false end function xr_combat_camper.action_shoot:initialize() action_base.initialize(self) self.st.camper_combat_action = true xr_sound.set_sound_play(self.object:id(),"fight_enemy") end function xr_combat_camper.action_shoot:execute() action_base.execute(self) local new_state = "hide_fire" local st = db.storage[self.object:id()] if (st and st.ini and st.active_section and st.ini:line_exist(st.active_section,"combat_camper_state_fire")) then new_state = st.ini:r_string_ex(st.active_section,"combat_camper_state_fire") end state_mgr.set_state(self.object,new_state,nil,nil,{look_object = self.object:best_enemy()},{fast_set = true}) xr_sound.set_sound_play(self.object:id(),"fight") end function xr_combat_camper.action_look_around:reset() self.forget_time = device():time_global() + 30000 self.change_dir_time = device():time_global() + 15000 -- если врага мы ещё не видели вообще, то всё равно повернуться к нему if not self.st.last_seen_pos and self.object:best_enemy() ~= nil then self.st.last_seen_pos = self.object:best_enemy():position() end local new_state = "hide" local st = db.storage[self.object:id()] if (st and st.ini and st.active_section and st.ini:line_exist(st.active_section,"combat_camper_state_look")) then new_state = st.ini:r_string_ex(st.active_section,"combat_camper_state_look") end state_mgr.set_state(self.object,new_state,nil,nil,{look_position = self.st.last_seen_pos}) end function xr_combat_camper.action_look_around:execute() action_base.execute(self) if (self.forget_time < device():time_global()) then -- self.object:enable_memory_object( self.object:best_enemy(), false ) self.st.last_seen_pos = nil return end if (self.change_dir_time < device():time_global()) then self.change_dir_time = device():time_global() + math.random(2000,4000) local ang = math.random(0,120) - 60 local dir = self.st.last_seen_pos and vector():set(self.st.last_seen_pos):sub(self.object:position()):normalize() dir = dir and vector_rotate_y(dir,ang):normalize() dir = dir and vector():set(dir.x * 10, dir.y * 10, dir.z * 10) local new_state = "hide" local st = db.storage[self.object:id()] if (st and st.ini and st.active_section and st.ini:line_exist(st.active_section,"combat_camper_state_look")) then new_state = st.ini:r_string_ex(st.active_section,"combat_camper_state_look") end state_mgr.set_state(self.object,new_state,nil,nil,{look_position = dir and self.object:position():add(dir)},{fast_set = true}) end end