Divergent/mods/Western Goods/gamedata/scripts/xr_logic_ex.script

1319 lines
48 KiB
Plaintext
Raw Normal View History

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