986 lines
31 KiB
Plaintext
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
|