--[[ scheme_type: generic author: Stohe modified_by: Alundaio --]] fighting_with_actor_npcs = {} safe_zone_npcs = {} local ignored_zone = {} local ignored_zone_list = { --"bar_mutant_exile_zone", --"esc_smart_terrain_2_12_sr_no_assault", --"esc_lager_guard_kill_zone", "zat_a2_sr_no_assault", "jup_a6_sr_no_assault", "jup_b41_sr_no_assault" } ------------------------------------------ -- Localized Functions ------------------------------------------ local function npc_on_hit_callback(npc,amount,local_direction,who,bone_index) if (amount > 0) then db.storage[npc:id()].hitted_by = who and who:id() end -- xr_bribe if who:id() == AC_ID then xr_bribe.npc_on_hit_callback(character_community(npc)) end end local function npc_on_death_callback(npc,who) safe_zone_npcs[npc:id()] = nil end local function squad_on_npc_death(squad,npc) if (squad:npc_count() == 0) then safe_zone_npcs[squad.id] = nil end end local function on_game_load() if (not alife_storage_manager.get_state().enable_warfare_mode) then for i=1,#ignored_zone_list do --ignored_zone[#ignored_zone+1] = ignored_zone_list[i] --Test end end end -------------------------------- -- Callback Register -------------------------------- function on_game_start() RegisterScriptCallback("npc_on_hit_callback",npc_on_hit_callback) RegisterScriptCallback("npc_on_death_callback",npc_on_death_callback) RegisterScriptCallback("squad_on_npc_death",squad_on_npc_death) RegisterScriptCallback("on_game_load",on_game_load) end math.randomseed(os.time()) local rnd = math.random(180000,480000) local rnd2 = rnd - 10000 local dist_rnd = math.random(18000,33000) function is_enemy(obj,enemy,no_memory) --utils_data.debug_write("eval_is_enemy") if(device().precache_frame > 1) then return false -- fix actor dying before game loads from enemies. Also fixes strange crash on game load when in combat sometimes. end if (obj:clsid() == clsid.crow) or (enemy:clsid() == clsid.crow) then -- freakin crows! return false end if not (obj:alive()) then return false end if not (enemy:alive()) then return false end if (obj:critically_wounded()) then return true end local flags = {override = false, result = false} SendScriptCallback("on_enemy_eval",obj,enemy,flags) if flags.override then return flags.result end local ene_id = enemy:id() local id = obj:id() local st = db.storage[id] if (DEV_DEBUG) then if (xrs_debug_tools and xrs_debug_tools.debug_invis and ene_id == AC_ID) then return false end end -- xr_bribe if xr_bribe and ene_id == AC_ID then if xr_bribe.at_peace(character_community(obj),character_community(enemy),enemy:position():distance_to_sqr(obj:position())) then return false end end local obj_is_stalker = IsStalker(obj) -- Ignore long unseen/unheard enemies if (obj_is_stalker) then if (no_memory ~= true) then local tg = time_global() local time_in_memory = tg - obj:memory_time(enemy) if (time_in_memory < 0) then --obj:enable_memory_object(enemy,false) --time_in_memory = time_in_memory + load_var(obj,"mem_time_offset",0) if (ene_id == 0 and time_in_memory < rnd*(-1) or time_in_memory < rnd2*(-1)) then return false end else if (ene_id == AC_ID and time_in_memory>rnd or time_in_memory>rnd2) or (enemy:position():distance_to_sqr(obj:position())>dist_rnd and not enemy:see(obj)) then --obj:enable_memory_object(enemy,false) --save_var(obj,"mem_time_offset",tg) return false end end end end -- NPC was hit by actor always return true. It's for keep_when_attacked condition if (ene_id == AC_ID and load_var(obj,"xr_combat_ignore_enabled",true) == false) then st.enemy_id = ene_id return true end if (obj:has_info("npcx_is_companion") or enemy:has_info("npcx_is_companion")) then -- If companion is in stealth mode then ignore combat greater than 30m unless enemy is in combat with actor if (obj:has_info("npcx_beh_substate_stealth") or enemy:has_info("npcx_beh_substate_stealth")) then if not (fighting_with_actor_npcs[id] or fighting_with_actor_npcs[ene_id]) then if (enemy:position():distance_to_sqr(obj:position()) < 900) then return false end end end else local is_actor = (ene_id == AC_ID) -- Ignore enemies during surge, only if npc has a surge job at a smart_terrain and the enemy is greater then 25m (actor 100m) away from the npc's surge job's alife_task position if (xr_conditions.surge_started) then local smart = xr_gulag.get_npc_smart(obj) local npc_info = smart and smart.npc_info and smart.npc_info[id] if (npc_info) then local npc_job = npc_info.job if (npc_job and npc_job.job_type_id == 2) then local alife_task = npc_job.alife_task if (alife_task) then if (is_actor and enemy:position():distance_to_sqr(alife_task:position()) > 10000) then return false elseif (enemy:position():distance_to_sqr(alife_task:position()) > 625) then return false end end end end end local sim = alife() local is_monster = character_community(enemy) == "zombied" or IsMonster(enemy) -- Ignore enemies with the safe_zone flag if (is_actor ~= true) then -- ignore hostages if (axr_task_manager.hostages_by_id[ene_id]) then return true end if (obj_is_stalker and not is_monster) then local se_obj = sim:object(id) if not (se_obj) then return false end local cid = se_obj.group_id and se_obj.group_id ~= 65535 and se_obj.group_id or id local tg = time_global() if (safe_zone_npcs[cid]) then db.storage[id].heli_enemy_flag = nil if (tg < safe_zone_npcs[cid] + 8000) then return false end safe_zone_npcs[cid] = nil else for i,v in ipairs(ignored_zone) do if (utils_obj.npc_in_zone(obj, v)) then safe_zone_npcs[cid] = tg return false end end end local ene_squad = get_object_squad(enemy) local bid = ene_squad and ene_squad.id or ene_id if (safe_zone_npcs[bid]) then return false else for i,v in ipairs(ignored_zone) do if (utils_obj.npc_in_zone(enemy, v)) then safe_zone_npcs[bid] = tg return false end end end end end --Ignore by distance; mainly unrestricted for all non-story stalker vs. stalker combat local pos1 = obj:position() local pos2 = enemy:position() local comm = character_community(obj) local ene_comm = character_community(enemy) if (comm == "army" and ene_comm == "stalker") or (comm == "stalker" and ene_comm == "army") then -- In Cordon prevent stalker v military combat with distances greater than 60m if (level.name() == "l01_escape" and pos1:distance_to_sqr(pos2) >= 3600) then return false end end if (is_actor) then if (level.get_time_hours() < 4 or level.get_time_hours() > 19 or level.rain_factor() >= 0.4) and (pos1:distance_to_sqr(pos2) > 10000) then -- limit combat range to 100m during night hours or heavy rain for stalker v actor return false end elseif (is_monster) then if (math.abs(pos1.y-pos2.y) > 30) then -- prevent stalker from attack mutants under ground or above ground return false end if (pos1:distance_to_sqr(pos2) > 4900) then -- ignore monsters 70m or greater return false end else local dist = pos1:distance_to_sqr(pos2) if (dist > 10000) then -- 100m max combat range for stalker v stalker return false end if (dist > 5625) and (level.rain_factor() > 0 or level.get_time_hours() < 4 or level.get_time_hours() > 19) then -- limit combat range to 75m during night hours for stalker v stalker or rain return false end if (dist > 3600) then -- limit combat range to 60m during heavy rain, surges or if they are story related if (xr_conditions.surge_started() or level.rain_factor() >= 0.5 or get_object_story_id(id) or get_object_story_id(ene_id)) then return false end local squad = get_object_squad(obj) local ene_squad = get_object_squad(enemy) if (squad and get_object_story_id(squad.id) or ene_squad and get_object_story_id(ene_squad.id)) then return false end end end end -- Store Pure enemy (Enemy without overrides) if (obj:relation(enemy) >= game_object.enemy) then st.enemy_id = ene_id end -- Ignore enemies by overrides if (ignore_enemy_by_overrides(obj,enemy)) then return false end return true end function 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 if (st) then -- 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 -- Ignore enemies because of no_combat_job elseif (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 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 return false end function npc_in_safe_zone(npc) if (npc:id() == AC_ID) then return false end local squad = get_object_squad(npc) if (squad and safe_zone_npcs[squad.id]) or (safe_zone_npcs[id]) then return true end return false end ---------------------------------------------------------------------------------------------------------------------- class "action_process_enemy" function action_process_enemy:__init( obj, storage ) --printf("action_process_enemy init: %s", obj:name()) self.object = obj self.st = storage end function action_process_enemy:trader_enemy_callback(obj,enemy) return false end function action_process_enemy:enemy_callback(npc, enemy) --utils_data.debug_write(strformat("action_process_enemy:enemy_callback")) local id = npc:id() local ene_id = enemy:id() --printf("action_process_enemy enemy_callback: %s, %s", npc:name(), enemy:name()) if (is_enemy(npc,enemy)) then --printf("!action_process_enemy enemy_callback: enemy detected") --utils_data.debug_write(strformat("action_process_enemy:enemy_callback is_enemy true")) -- keep track of actor enemies if (ene_id == AC_ID and not fighting_with_actor_npcs[id]) then fighting_with_actor_npcs[id] = true SendScriptCallback("npc_on_fighting_actor",npc) end -- Set smart alarm (unused in coc) --[[ local sim = alife() local se_obj = sim:object(id) if (se_obj and se_obj.m_smart_terrain_id ~= 65535) then local smart = sim:object(se_obj.m_smart_terrain_id) if (smart) then smart:set_alarm() end end --]] return true end db.storage[id].enemy_id = nil if (ene_id == AC_ID) then fighting_with_actor_npcs[id] = nil save_var(npc,"xr_combat_ignore_enabled",nil) end --utils_data.debug_write(strformat("action_process_enemy:enemy_callback is_enemy false")) --printf("-action_process_enemy enemy_callback: no enemy") return false end function action_process_enemy:hit_callback(obj, amount, local_direction, who, bone_index) if (load_var(obj,"xr_combat_ignore_enabled") == false) then return end if (amount > 0 and who and who:id() == AC_ID) then local sim = alife() local se_obj = sim:object(obj:id()) if (se_obj) then local squad = se_obj.group_id and se_obj.group_id ~= 65535 and sim:object(se_obj.group_id) if (squad) then -- disable combat ignore for entire squad if attacked by actor for k in squad:squad_members() do local st = db.storage[k.id] if (st) then if (st.overrides == nil or st.overrides.combat_ignore_keep_when_attacked ~= true) then --st.combat_ignore.enabled = false save_var(st.object,"xr_combat_ignore_enabled",false) end end end else local overrides = db.storage[obj:id()] and db.storage[obj:id()].overrides if not overrides or not overrides.combat_ignore_keep_when_attacked then --self.st.enabled = false save_var(obj,"xr_combat_ignore_enabled",false) end end end end end ---------------------------------------------------------------------------------------------------------------------- -- binder ---------------------------------------------------------------------------------------------------------------------- function setup_generic_scheme(npc,ini,scheme,section,stype,temp) local st = xr_logic.assign_storage_and_bind(npc,ini,"combat_ignore",section,temp) end function add_to_binder(npc,ini,scheme,section,st,temp) st.action = action_process_enemy(npc,st) end function reset_generic_scheme(npc,scheme,section,stype,st) local storage = st.combat_ignore if not (storage) then printf("xr_combat_ignore: reset_generic_scheme storage is nil! npc=%s scheme=%s section=%s",npc and npc:name(),scheme,section) return end npc:set_enemy_callback(storage.action.enemy_callback,storage.action) xr_logic.subscribe_action_for_events(npc, storage, storage.action) --storage.enabled = true end function disable_generic_scheme(npc,scheme,stype) npc:set_enemy_callback() local st = db.storage[npc:id()][scheme] if st then xr_logic.unsubscribe_action_from_events(npc, st, st.action) end end