452 lines
14 KiB
Plaintext
452 lines
14 KiB
Plaintext
--[[
|
|
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
|