Divergent/mods/Dead Air XR Danger/gamedata/scripts/xr_danger.script

986 lines
31 KiB
Plaintext

--[[
scheme_type: generic
author: Alundaio
modified_by:
--]]
--------------------
evaid = 188113
actid = 188113
--------------------
--------------------
local alife, math_random, tonumber = alife, math.random, tonumber
--------------------
local xr_weight_torch_detection = false -- DONT ENABLE
local ini = ini_file("ai_tweaks\\xr_danger.ltx")
DangerInertion = utils_data.collect_section(ini,"danger_inertion",true)
DangerInertionActor = utils_data.collect_section(ini,"danger_inertion_actor",true)
DangerIgnore = utils_data.collect_section(ini,"danger_object",true)
DangerIgnoreActor = utils_data.collect_section(ini,"danger_object_actor",true)
ini = nil
--[[
danger:perceive_type()
[danger_object.hit] = "hit",
[danger_object.sound] = "sound",
[danger_object.visual] = "visual"
--]]
local script_danger = {}
local bd_types = {
[danger_object.grenade] = "grenade",
[danger_object.entity_corpse] = "entity_corpse",
[danger_object.entity_attacked] = "entity_attacked",
[danger_object.attacked] = "attacked",
[danger_object.bullet_ricochet] = "bullet_ricochet",
[danger_object.enemy_sound] = "enemy_sound",
[danger_object.attack_sound] = "attack_sound",
[danger_object.entity_death] = "entity_death",
}
-----------------------------------------------------------------------------
-- PUBLIC
-----------------------------------------------------------------------------
function set_script_danger(npc,time,who_id,pos)
local id = npc:id()
if not (script_danger[id]) then
script_danger[id] = {danger_time = time_global(), danger_inertion = time, id = who_id, position = pos}
else
script_danger[id].danger_time = time_global()
script_danger[id].danger_inertion = time
script_danger[id].id = who_id
script_danger[id].position = pos
end
end
function has_danger(npc)
local id = npc:id()
local st = db.storage[id]
return st and st.danger_flag == true
end
-----------------------------------------------------------------------------
-- PRIVATE
-----------------------------------------------------------------------------
local function get_danger_name(best_danger)
local best_danger_object = best_danger:object()
local bd_type = best_danger:type()
if bd_type ~= danger_object.grenade and best_danger:dependent_object() ~= nil then
best_danger_object = best_danger:dependent_object()
end
if best_danger_object == nil then
return "none"
end
return best_danger_object:name()
end
function get_danger_time(danger,npc)
if (danger:type() == danger_object.entity_corpse) then
local corpse_object = danger:object()
return corpse_object and IsStalker(corpse_object) and corpse_object:death_time() or 0
end
return danger:time()
end
local npc_update_time = 0
local function npc_on_update(npc)
if not xr_weight_torch_detection then return end
if (npc:see(db.actor)) then return end
if (npc_update_time > time_global()) then return end
npc_update_time = time_global()+100
if not (IsStalker(npc)) then
return
end
if (has_danger(npc)) then
return
end
if (npc:best_enemy()) then
return
end
if (IsWounded(npc) or npc:character_community() == "zombied" or xr_combat_ignore.npc_in_safe_zone(npc)) then
return
end
local dist = npc:position():distance_to_sqr(db.actor:position())
local actor_weight = (db.actor:get_total_weight() - 25)*6
local diff = actor_weight/dist
if (diff > 1) and (dist < 200) then
npc:make_object_visible_somewhen(db.actor)
--printf("DETECT ACTOR WEIGHT:"..diff)
end
if (itms_manager.Torch2) and (dist < 80) and (npc:get_luminocity() < 0.3) then
npc:make_object_visible_somewhen(db.actor)
--printf("DETECT ACTOR TORCH")
end
end
local function npc_on_hear_callback(npc,who_id,s_type,dist,sound_power,sound_position)
if not (IsStalker(npc)) then
return
end
if (has_danger(npc)) then
return
end
if (npc:best_enemy()) then
return
end
--utils_data.debug_write("xr_danger:hear_callback",true)
if (IsWounded(npc) or character_community(npc) == "zombied" or xr_combat_ignore.npc_in_safe_zone(npc)) then
return
end
if (s_type == "WPN_hit" or s_type == "MST_attack" or s_type == "MST_damage") and dist < 900 then
local who = who_id and (db.storage[who_id] and db.storage[who_id].object)
if not (who) then
return
end
if not (IsStalker(who) or IsMonster(who)) then
return
end
if (xr_combat_ignore.ignore_enemy_by_overrides(npc,who)) then
return
end
if (who_id == AC_ID or npc:relation(who) >= 2) then
set_script_danger(npc,7000,who_id,vec_set(sound_position))
end
end
end
local function npc_on_death_callback(npc,who)
if (IsStalker(npc) and character_community(npc) ~= "zombied") then
--save_var(npc,"death_by_id",who:id())
local st = db.storage[npc:id()]
if (st) then
st.killer_last_known_position = vector():set(who:position())
end
end
end
-- xcvb this one looks weird, need testing because npcs should hear the body sound anyway
--[[
local function npc_on_hit_callback(npc,amount,local_direction,who,bone_index)
if (bone_index == "from_death_callback") then
return
end
set_script_danger(npc,7000,who_id,who and vector():set(who:position()))
end
--]]
local function get_rand_vertex(npc,vo)
local dir = npc:position():sub(vo:position())
dir = vector_rotate_y(dir,math_random(MinVectorRotateY,MaxVectorRotateY))
return vo:vertex_in_direction(vo:level_vertex_id(),dir,math_random(MinDistance,MaxDistance))
end
function danger_in_radius(npc,best_danger,bd_type)
local from_actor = (best_danger:object() and (best_danger:object():id() == db.actor:id()))
local src = (from_actor and DangerIgnoreActor[bd_types[bd_type]]) or DangerIgnore[bd_types[bd_type]]
local condlist = xr_logic.parse_condlist(npc,bd_types[bd_type],"danger_object",src)
local ignore_distance = condlist and tonumber(xr_logic.pick_section_from_condlist(db.actor,npc,condlist)) or 0
if (ignore_distance == 0) then
return false
end
if (best_danger:position():distance_to_sqr(npc:position()) < ignore_distance) then
return true
end
return false
end
local function is_danger(npc,best_danger,script)
if xr_wounded.is_heavy_wounded_by_id(npc:id()) then
return false
end
if (xrs_kill_wounded and axr_task_manager.hostages_by_id[npc:id()]) then
return false
end
local best_danger_object = best_danger:object()
local bd_type = best_danger:type()
--if (bd_type == danger_object.entity_corpse) then
--return false
--end
if (bd_type == danger_object.grenade) then
if not (best_danger:dependent_object() and character_community(npc) ~= "zombied") then
return false
end
return danger_in_radius(npc,best_danger,bd_type)
end
if (xr_combat_ignore.npc_in_safe_zone(npc)) then
return false
end
if (bd_type == danger_object.entity_corpse) then
if (best_danger_object and IsStalker(best_danger_object) and character_community(best_danger_object) == character_community(npc)) then
local corpse_st = db.storage[best_danger_object:id()]
if not (corpse_st and corpse_st.death_time and corpse_st.death_by_id) then
return false
end
local killer = db.storage[corpse_st.death_by_id] and db.storage[corpse_st.death_by_id].object
if not (killer and (IsMonster(killer) or IsStalker(killer)) and killer:alive() and npc:relation(killer) > 0 and character_community(killer) ~= character_community(npc)) then
return false
end
if (xr_combat_ignore.ignore_enemy_by_overrides(npc,killer)) then
return false
end
if (game.get_game_time():diffSec(corpse_st.death_time) > 120000) then
return false
end
return danger_in_radius(npc,best_danger,bd_type)
end
return false
end
--if (db.storage[npc:id()].active_scheme == "camper") then
--return false
--end
if (best_danger:dependent_object()) then
best_danger_object = best_danger:dependent_object()
end
if (best_danger_object and (best_danger:perceive_type() == danger_object.hit)) then -- bypass combat ignore radius for attacked, entity_attacked, ricochet
return danger_in_radius(npc,best_danger,bd_type)
end
--if not (best_danger_object and (IsMonster(best_danger_object) or IsStalker(best_danger_object)) and npc:relation(best_danger_object) >= 2) then
if not (best_danger_object and (IsMonster(best_danger_object) or IsStalker(best_danger_object)) and ((best_danger:object():id() == db.actor:id()) or (npc:relation(best_danger_object) >= 2))) then
return false
end
if (xr_corpse_detection.is_under_corpse_detection(npc) or xr_help_wounded.is_under_help_wounded(npc)) then
return false
elseif (xrs_kill_wounded and xrs_kill_wounded.is_under_kill_wounded(npc)) then
return false
end
if (best_danger_object and (IsMonster(best_danger_object) or IsStalker(best_danger_object))) then
if not (best_danger_object:alive() and xr_combat_ignore.is_enemy(npc, best_danger_object,true)) then
return false
end
end
return danger_in_radius(npc,best_danger,bd_type)
end
local function check_script_danger(id)
if (script_danger[id]) then
if (time_global() < script_danger[id].danger_time + script_danger[id].danger_inertion) then
return true
else
script_danger[id] = nil
end
end
return false
end
-----------------------------------------------------------------------------
-- ON GAME START
-----------------------------------------------------------------------------
function on_game_start()
RegisterScriptCallback("npc_on_update",npc_on_update)
-- RegisterScriptCallback("npc_on_hit_callback",npc_on_hit_callback)
RegisterScriptCallback("npc_on_hear_callback",npc_on_hear_callback)
RegisterScriptCallback("npc_on_death_callback",npc_on_death_callback)
end
----------------------------------------------------------------------------------------------------------------------
-- EVALUATORS
----------------------------------------------------------------------------------------------------------------------
class "evaluator_danger" (property_evaluator)
function evaluator_danger:__init(name, storage, npc) super (nil, name)
self.a = storage
end
local function eval_danger(self)
local npc = self.object
local danger = npc:best_danger()
local flags = {ret_value = true}
SendScriptCallback("npc_on_eval_danger", self.object, flags)
if flags.ret_value == false then return false end
if not (danger) then
return false
end
local from_actor = (danger:object() and (danger:object():id() == db.actor:id()))
local tg = time_global()
local bd_type = danger:type()
if (self.a.inertion) then
if (self.a.type == bd_type) then
if (tg < self.a.danger_time + self.a.inertion) then
return true
end
end
end
self.a.inertion = nil
--[[
if (bd_types[bd_type] == "entity_corpse") then
printf("%s %s %s",npc:name(),danger:object():name(),bd_types[bd_type])
end
--]]
local src = (from_actor and DangerInertionActor[bd_types[bd_type]]) or DangerInertion[bd_types[bd_type]]
local inertion = tonumber(xr_logic.pick_section_from_condlist(db.actor,npc,xr_logic.parse_condlist(npc,bd_types[bd_type],"danger_inertion_time",src))) or 0
local new_danger_time = get_danger_time(danger,npc)
if (tg >= new_danger_time + inertion) then
return false
end
if not(is_danger(npc,danger)) then
return false
end
--[[
local sim = alife()
local se_obj = sim:object(id)
if (se_obj and se_obj.m_smart_terrain_id ~= 65535) then
sim:object(se_obj.m_smart_terrain_id):set_alarm()
end
--]]
self.a.danger_time = new_danger_time
self.a.inertion = inertion
self.a.type = bd_type
return true
end
function evaluator_danger:evaluate()
--utils_data.debug_write("eva_danger")
local danger = eval_danger(self) or false
db.storage[self.object:id()].danger_flag = danger
return danger
end
-----------------------------------------------------------------------
-- ACTION
-----------------------------------------------------------------------
class "action_danger" ( action_base )
function action_danger:__init( name, storage ) super ( nil, name )
self.a = storage
end
function action_danger:initialize()
action_base.initialize( self )
self.a.stage = 1
self.object:set_path_type(game_object.level_path)
self.object:set_detail_path_type(move.line)
self.object:set_desired_direction()
self.a.last_state = state_mgr.get_state(self.object)
end
local function script_action_danger_scripted(npc,st)
--utils_data.debug_write("script_action_danger_scripted")
local id = npc:id()
local bdo = script_danger[id].id and db.storage[script_danger[id].id] and db.storage[script_danger[id].id].object
local bdo_pos = script_danger[id].position
local dist = bdo_pos and npc:position():distance_to_sqr(bdo_pos)
local tg = time_global()
local who_dist = bdo and bdo:position():distance_to_sqr(bdo_pos)
--[[
if (dist and dist >= 70) then
if (bdo and npc:relation(bdo) > 0 and npc:see(bdo)) then
state_mgr.set_state(npc,"hide_na",nil,nil,{look_object = bdo})
if (not utils_obj.squad_in_los(npc,bdo)) then
local wpn = npc:best_weapon()
if (wpn) then
local relation = npc:relation(bdo)
local look_pos = vector():set(bdo:position())
look_pos.y = relation >= 2 and look_pos.y + 1 or look_pos.y - (math_random(25,35)/100)
npc:set_sight(look.fire_point,look_pos)
if (npc:target_body_state() == move.crouch) then
st.__warning_shot_tmr = not st.__warning_shot_tmr and tg+500 or st.__warning_shot_tmr
if (tg < st.__warning_shot_tmr) then
npc:set_item(object.fire1,wpn,1,1)
xr_sound.set_sound_play(npc:id(),"tolls")
end
end
end
end
st.lvid = utils_obj.send_to_nearest_accessible_vertex(npc,npc:level_vertex_id())
end
end
--]]
if (st.stage == 1) then
local new_vid = random_choice(utils_obj.try_to_strafe(npc),utils_obj.try_go_backward(npc,6),utils_obj.try_go_cover(npc,bdo and bdo:position() or bdo_pos or npc:position()))
st.lvid = nil
if (new_vid and (not st.lvid or (new_vid ~= st.lvid and new_vid ~= npc:level_vertex_id())))then
st.lvid = new_vid
end
if not (new_vid) then
local dir = bdo_pos and vector():set(npc:position()):sub(bdo_pos) or npc:direction()
dir = vector_rotate_y(dir,math_random(-20,20))
local node = position_node(5)
node = node:select_best_vertex_id(npc,dir,npc:level_vertex_id(),20,35)
if (node and node.vertex_id and node.vertex_id ~= npc:level_vertex_id()) then
st.lvid = node.vertex_id
end
end
if (st.lvid and st.lvid ~= npc:level_vertex_id()) then
st.lvid = utils_obj.send_to_nearest_accessible_vertex(npc,st.lvid)
st.stage = 2
local sm = db.storage[npc:id()].state_mgr
if (sm and sm.target_state ~= "assault") then
npc:clear_animations()
sm:set_state("assault",nil,nil,{look_position = who_dist and who_dist < 22500 and bdo and bdo:position() or bdo_pos}, {fast_set = true})
sm.animation:set_state(nil, true)
sm.animation:set_control()
sm.animstate:set_state(nil,true)
sm.animstate:set_control()
end
return
end
state_mgr.set_state(npc,"hide_na",nil,nil,{look_position = who_dist and who_dist < 22500 and bdo and bdo:position() or bdo_pos})
elseif (st.stage == 2) then
if (npc:level_vertex_id() == st.lvid) then
local tg = time_global()
st.__dtimer = not st.__dtimer and tg + math_random(5000,15000) or st.__dtimer
if (tg > st.__dtimer) then
st.stage = 1
return
end
state_mgr.set_state(npc,"threat_danger",nil,nil,{look_position = who_dist and who_dist < 22500 and bdo and bdo:position() or bdo_pos})
return
end
state_mgr.set_state(npc,"assault",nil,nil,{look_position = who_dist and who_dist < 22500 and bdo and bdo:position() or bdo_pos})
utils_obj.send_to_nearest_accessible_vertex(npc,st.lvid)
end
end
local function script_action_danger_grenade(npc,st,bd,best_danger_object,dependent_object,bd_type)
--utils_data.debug_write("script_action_danger_grenade")
-- In this case dependent_object should be actual grenade object; best_danger_object should be nade thrower
local bdo = dependent_object or best_danger_object
local bdo_pos = bdo and bdo:position()
local dist = bdo_pos and npc:position():distance_to_sqr(bdo_pos)
local be = npc:best_enemy() or best_danger_object and npc:relation(best_danger_object) > 0 and best_danger_object
local tg = time_global()
if (dist and dist >= 150) then
--[[
if (be and npc:see(be)) then
state_mgr.set_state(npc,"hide_na",nil,nil,{look_object = be})
if (not utils_obj.squad_in_los(npc,be)) then
local wpn = npc:best_weapon()
if (wpn) then
local relation = npc:relation(be)
local look_pos = vector():set(be:position())
look_pos.y = relation >= 2 and look_pos.y + 1 or look_pos.y - (math_random(25,35)/100)
npc:set_sight(look.fire_point,look_pos)
if (npc:target_body_state() == move.crouch) then
st.__warning_shot_tmr = not st.__warning_shot_tmr and tg+500 or st.__warning_shot_tmr
if (tg < st.__warning_shot_tmr) then
npc:set_item(object.fire1,wpn,1,1)
xr_sound.set_sound_play(npc:id(),"tolls")
end
end
npc:set_body_state(move.crouch)
end
end
else
state_mgr.set_state(npc,"threat_danger",nil,nil,{look_position = bdo_pos})
end
--]]
local sm = db.storage[npc:id()].state_mgr
if (sm and sm.target_state ~= "threat_danger") then
npc:clear_animations()
sm:set_state("threat_danger",nil,nil,{look_position = bdo_pos}, {fast_set = true})
sm.animation:set_state(nil, true)
sm.animation:set_control()
sm.animstate:set_state(nil,true)
sm.animstate:set_control()
end
st.lvid = utils_obj.send_to_nearest_accessible_vertex(npc,npc:level_vertex_id())
return
end
if (st.stage == 1) then
local dir = bdo_pos and vector():set(npc:position()):sub(bdo_pos) or npc:direction()
dir = vector_rotate_y(dir,math_random(-20,20))
local new_vid = npc:vertex_in_direction(npc:level_vertex_id(),dir,math_random(20,35))
st.lvid = nil
if (new_vid and (not st.lvid or (new_vid ~= st.lvid and new_vid ~= npc:level_vertex_id())))then
st.lvid = new_vid
end
if not (new_vid) then
local node = position_node(5)
node = node:select_best_vertex_id(npc,dir,npc:level_vertex_id(),20,35)
if (node and node.vertex_id and node.vertex_id ~= npc:level_vertex_id()) then
st.lvid = node.vertex_id
end
end
if (st.lvid and st.lvid ~= npc:level_vertex_id()) then
st.lvid = utils_obj.send_to_nearest_accessible_vertex(npc,st.lvid)
st.stage = 2
local sm = db.storage[npc:id()].state_mgr
if (sm) then
npc:clear_animations()
sm:set_state("assault",nil,nil,{look_object = dist <= 100 and nil or bdo}, {fast_set = true})
sm.animation:set_state(nil, true)
sm.animation:set_control()
sm.animstate:set_state(nil,true)
sm.animstate:set_control()
end
return
end
state_mgr.set_state(npc,"hide_na",nil,nil,{look_object = be and npc:see(be) and be or bdo},{fast_set = true})
elseif (st.stage == 2) then
if (npc:level_vertex_id() == st.lvid) then
st.stage = 1
return
end
state_mgr.set_state(npc,"assault",nil,nil,{look_object = dist <= 100 and nil or bdo})
utils_obj.send_to_nearest_accessible_vertex(npc,st.lvid)
end
end
local function script_action_danger_corpse(npc,st,bd,best_danger_object,dependent_object,bd_type)
--utils_data.debug_write("script_action_danger_corpse")
-- In this case corpse is best_danger_object; dependent_object should be killer or it's probably nil
local bdo = best_danger_object or dependent_object
local dst = db.storage[bdo:id()]
local killer = dependent_object or dst and dst.death_by_id and (db.storage[dst.death_by_id] and db.storage[dst.death_by_id].object)
-- force hostile to killer
if (st.stage >= 2 and killer and killer:id() == 0 and killer:alive() and npc:relation(killer) >= game_object.neutral and npc:see(killer)) then
local squad = get_object_squad(npc)
if (squad) then
for k in squad:squad_members() do
npc = db.storage[k.id] and db.storage[k.id].object or level.object_by_id(k.id)
if(npc) then
npc:force_set_goodwill( -1000, killer)
end
end
else
npc:force_set_goodwill( -1000, killer)
end
--printf("xr_danger.script_action_danger_corpse: forced enemies %s and %s",npc:name(), killer:name())
elseif (killer and killer:id() == 0 and killer:alive() and npc:relation(killer) < game_object.neutral) then
npc:make_object_visible_somewhen(killer)
end
--printf("stage = %s",st.stage)
if (st.stage == 1) then
if (math.random(1,100) < 50) then
st.stage = 3
end
end
if (st.stage == 1) then
st.lvid = bdo:level_vertex_id()
st.lvid = utils_obj.send_to_nearest_accessible_vertex(npc,st.lvid)
state_mgr.set_state(npc,"assault",nil,nil,{look_object = bdo}, {fast_set = true})
st.rnd = math_random(30)
st.stage = 2
elseif (st.stage == 2) then
if not (st.rnd and st.lvid) then
st.stage = 1
return
end
local dist = bdo and bdo:position():distance_to_sqr(npc:position()) or 0
if (st.lvid == npc:level_vertex_id() or dist <= st.rnd) then
local rnd = math_random(100)
if (rnd <= 33) then
xr_sound.set_sound_play(npc:id(),"search")
state_mgr.set_state(npc,"threat_danger",nil,nil,{look_position = dst and dst.killer_last_known_position})
st.dtimer = time_global() + math_random(3000,5000)
elseif (dist <= 5 and not st.searched) then
st.searched = true
state_mgr.set_state(npc,"search")
st.dtimer = time_global() + math_random(3000,5000)
else
state_mgr.set_state(npc,"raid",nil,nil,{look_position = dst and dst.killer_last_known_position}, {fast_set = true})
end
st.stage = 3
return
end
st.lvid = utils_obj.send_to_nearest_accessible_vertex(npc,st.lvid)
state_mgr.set_state(npc,"assault",nil,nil,{look_position = dst and dst.killer_last_known_position}, {fast_set = true})
elseif (st.stage == 3) then
if (st.dtimer and time_global() < st.dtimer) then
return
end
local dist_to_killer = dst and dst.killer_last_known_position and npc:position():distance_to_sqr(dst.killer_last_known_position) or 30
local new_vid = random_choice(utils_obj.try_to_strafe(npc),utils_obj.try_go_backward(npc,6),utils_obj.try_go_cover(npc,dst and dst.killer_last_known_position or bdo and bdo:position() or npc:position()))
if (new_vid and (not st.lvid or new_vid ~= st.lvid))then
st.lvid = new_vid
end
if not (new_vid) then
local dir = dst and dst.killer_last_known_position and vector():set(dst.killer_last_known_position):sub(npc:position()) or killer and vector():set(killer:position()):sub(npc:position()) or npc:direction()
local node = position_node(3)
node = node:select_best_vertex_id(npc,dir,npc:level_vertex_id(),10,dist_to_killer/2)
if (node and node.vertex_id and node.vertex_id ~= npc:level_vertex_id()) then
st.lvid = node.vertex_id
end
end
if (st.lvid) then
st.lvid = utils_obj.send_to_nearest_accessible_vertex(npc,st.lvid)
state_mgr.set_state(npc,"assault",nil,nil,{look_position = dst and dst.killer_last_known_position}, {fast_set = true})
st.stage = 4
return
end
st.dtimer = time_global() + math_random(5000,7000)
st.stage = 5
state_mgr.set_state(npc,"threat_danger",nil,nil,{look_position = dst and dst.killer_last_known_position}, {fast_set = true})
return
elseif (st.stage == 4) then
if (npc:level_vertex_id() == st.lvid) then
st.dtimer = time_global() + math_random(5000,7000)
st.stage = 5
state_mgr.set_state(npc,"hide_na",nil,nil,{look_position = dst and dst.killer_last_known_position}, {fast_set = true})
return
end
st.lvid = utils_obj.send_to_nearest_accessible_vertex(npc,st.lvid)
elseif (st.stage == 5) then
if (st.dtimer and time_global() < st.dtimer) then
return
end
st.lvid = nil
st.stage = 6
elseif (st.stage == 6) then
local new_vid = utils_obj.try_go_cover(npc,dst and dst.killer_last_known_position or bdo and bdo:position() or npc:position())
if (new_vid) then
st.lvid = utils_obj.send_to_nearest_accessible_vertex(npc,st.lvid)
state_mgr.set_state(npc,"hide_na",nil,nil,{look_position = dst and dst.killer_last_known_position or dst.object and dst.object:position()}, {fast_set = true})
end
end
end
local function script_action_danger_attacked(npc,st,bd,best_danger_object,dependent_object,bd_type)
--utils_data.debug_write("script_action_danger_attacked")
local bdo = dependent_object or best_danger_object
local tg = time_global()
--printf("stage = %s",st.stage)
if (st.stage == 1) then
if not (st.last_pos) then
st.last_pos = bdo and vector():set(bdo:position())
end
local dir = st.last_pos and vector():set(st.last_pos):sub(npc:position()) or vector():set(npc:direction())
local dist_to_bdo = st.last_pos and npc:position():distance_to_sqr(st.last_pos) or 30
local target_vertex_id = random_choice(utils_obj.try_to_strafe(npc),utils_obj.try_go_backward(npc,6),utils_obj.try_go_cover(npc,st.last_pos or npc:position()))
if (target_vertex_id) then
st.lvid = utils_obj.send_to_nearest_accessible_vertex(npc,target_vertex_id)
state_mgr.set_state(npc,"assault",nil,nil,{look_position = st.last_pos})
st.stage = 2
return
end
st.dtimer = tg + math_random(5000,6000)
st.stage = 3
return
elseif (st.stage == 2) then
if (npc:level_vertex_id() == st.lvid) then
st.dtimer = tg + math_random(5000,7000)
st.stage = 3
return
end
st.lvid = utils_obj.send_to_nearest_accessible_vertex(npc,st.lvid)
elseif (st.stage == 3) then
if (st.dtimer and tg < st.dtimer) then
state_mgr.set_state(npc,"threat_danger",nil,nil,{look_position = st.last_pos})
return
end
st.stage = 1
end
end
function action_danger:execute()
action_base.execute(self)
--utils_data.debug_write("BEFORE scripted_danger_action_planner",true)
local npc = self.object
local best_danger = npc:best_danger()
if not (best_danger) then
return
end
if (check_script_danger(npc:id())) then
script_action_danger_scripted(npc,self.a)
return
end
if (npc:path_type() ~= game_object.level_path) then
npc:set_path_type(game_object.level_path)
end
local best_danger_object = best_danger:object()
local bd_type = best_danger:type()
local dependent_object = best_danger:dependent_object()
if (bd_type == danger_object.grenade) then
script_action_danger_grenade(npc,self.a,best_danger,best_danger_object,dependent_object,bd_type)
elseif (bd_type == danger_object.entity_corpse) then
script_action_danger_corpse(npc,self.a,best_danger,best_danger_object,dependent_object,bd_type)
elseif (bd_type == danger_object.attacked) then
script_action_danger_attacked(npc,self.a,best_danger,best_danger_object,dependent_object,bd_type)
end
--utils_data.debug_write("AFTER scripted_danger_action_planner")
end
function action_danger:finalize()
self.a.stage = 1
if (self.a.last_state) then
state_mgr.set_state(self.object,self.a.last_state)
end
for k,v in pairs(db.used_level_vertex_ids) do
db.used_level_vertex_ids[k] = nil
end
self.a.__warning_shot_tmr = nil
utils_obj.send_to_nearest_accessible_vertex(self.object,self.object:level_vertex_id())
action_base.finalize(self)
end
----------------------------------------------------------------------------------------------------------------------
-- false - engine danger, true - script danger
----------------------------------------------------------------------------------------------------------------------
class "evaluator_check_danger" (property_evaluator)
function evaluator_check_danger:__init(name, storage) super (nil, name)
self.a = storage
end
function evaluator_check_danger:evaluate()
--utils_data.debug_write("eva_check_danger")
if (self.a.disable) then
return false
end
if not (IsStalker(self.object)) then
return false
end
if not (self.object:alive()) then
return false
end
if (self.object:best_enemy()) then
return false
end
if (IsWounded(self.object)) then
return false
end
if (character_community(self.object) == "zombied") then
return false
end
local bd = self.object:best_danger()
if not (bd) then
return false
end
local id = self.object:id()
if not (db.storage[id].danger_flag) then
return false
end
if (check_script_danger(id)) then
return true
end
local bd_type = bd:type()
if (self.object:has_info("npcx_is_companion") and bd_type == danger_object.entity_corpse) then
return false
end
if not (bd_type == danger_object.attacked or bd_type == danger_object.entity_corpse or bd_type == danger_object.grenade) then
return false
end
return true
end
class "position_node"
function position_node:__init(amt)
self.node = {}
for i=1,amt do
self.node[i] = {}
end
end
function position_node:select_best_vertex_id(object,dir,lvid,min_dist,max_dist,find_furthest)
local close
min_dist = min_dist and (min_dist <= max_dist and min_dist or max_dist) or 10
max_dist = max_dist and (max_dist >= min_dist and max_dist or min_dist) or 30
for i=1, #self.node do
local direction = vector_rotate_y(vec_set(dir), math_random(-180,180))
self.node[i].vertex_id = level.vertex_in_direction(lvid, direction, math_random(min_dist,max_dist) )
if (self.node[i].vertex_id and utils_obj.accessible(object,self.node[i].vertex_id) and self.node[i].vertex_id ~= lvid) then
self.node[i].distance = self.node[i].vertex_id == lvid and -1 or object:position():distance_to_sqr(level.vertex_position(self.node[i].vertex_id))
if not (close) then
close = i --self.node[i].vertex_id
end
if (close) then
if (find_furthest) then
if (self.node[close] and self.node[close].distance < self.node[i].distance) then
close = i
end
else
if (self.node[close] and self.node[i].distance < self.node[close].distance) then
close = i
end
end
end
end
end
return close and self.node[close]
end
----------------------------------------------------------------------------------------------------------------------
-- BINDER
----------------------------------------------------------------------------------------------------------------------
function setup_generic_scheme(npc,ini,scheme,section,stype,temp)
local st = xr_logic.assign_storage_and_bind(npc,ini,"danger","danger",temp)
db.storage[npc:id()].danger_flag = false
db.storage[npc:id()].danger_flag_scripted = nil
end
function add_to_binder(npc,ini,scheme,section,storage,temp)
local manager = npc:motivation_action_manager()
manager:remove_evaluator(stalker_ids.property_danger)
manager:add_evaluator(stalker_ids.property_danger,evaluator_danger("danger", storage, npc))
manager:add_evaluator(evaid,evaluator_check_danger("script_danger",storage,npc))
local danger_action = manager:action(stalker_ids.action_danger_planner)
local danger_action_planner = cast_planner(danger_action)
danger_action_planner:remove_evaluator(stalker_ids.property_danger)
danger_action_planner:add_evaluator(stalker_ids.property_danger,evaluator_danger("danger", storage, npc))
local wp = world_property
danger_action:add_precondition( wp(evaid, false) )
temp.action = action_danger("danger", storage)
temp.action:add_precondition( wp(evaid, true) )
temp.action:add_precondition( wp(stalker_ids.property_danger, true) )
temp.action:add_effect( wp(evaid,false) )
--temp.action:add_effect( wp(stalker_ids.property_danger, false) )
manager:add_action(actid,temp.action)
end
function configure_actions(npc,ini,scheme,section,stype,temp)
local wp = world_property
temp.action:add_precondition( wp(stalker_ids.property_alive, true) )
temp.action:add_precondition( wp(stalker_ids.property_enemy, false) )
temp.action:add_precondition( wp(stalker_ids.property_anomaly, false) )
local manager = npc:motivation_action_manager()
local action
local p = {
xr_actions_id.alife,
--xr_actions_id.state_mgr + 1,
xr_actions_id.state_mgr + 2
}
for i=1,#p do
action = manager:action(p[i])
if (action) then
action:add_precondition( wp(evaid,false) )
else
printf("xr_danger: no action id p[%s]",i)
end
end
end
function reset_generic_scheme(npc,scheme,section,stype,st)
end