2181 lines
62 KiB
Plaintext
2181 lines
62 KiB
Plaintext
|
--[[
|
||
|
|
||
|
by Alundaio
|
||
|
|
||
|
Copyright (C) 2012 Alundaio
|
||
|
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License
|
||
|
|
||
|
-------------
|
||
|
|
||
|
Tronex
|
||
|
Last modification: 2020/1/20
|
||
|
|
||
|
Dimissing/releasing companions and special squads is done here with safe releasing and fade out effect
|
||
|
Companions wheel:
|
||
|
Fast interaction with companions
|
||
|
Suppor for trade button
|
||
|
Command individual or all companions
|
||
|
|
||
|
-------------
|
||
|
|
||
|
Hive Game
|
||
|
Last modification: 2023/4/15
|
||
|
|
||
|
Companion groups
|
||
|
Companions list
|
||
|
|
||
|
--]]
|
||
|
|
||
|
-- SEE configs\ai_tweaks\axr_companions
|
||
|
allow_only_friends_as_companions = true
|
||
|
allow_simulation_squads_as_companions = false
|
||
|
max_actor_squad_size = 0 -- used only for non task companions
|
||
|
|
||
|
-- Companion tables. All companions are added to companion_squads table by their squad id. Only non-task companions are tracked by non_task_companions table.
|
||
|
companion_squads = {}
|
||
|
non_task_companions = {}
|
||
|
------------------------------------------
|
||
|
local assigned_items = {}
|
||
|
|
||
|
local states_squad_0 = {
|
||
|
["combat"] = 1,
|
||
|
["move"] = 2,
|
||
|
["stealth"] = 2,
|
||
|
["loot"] = 1,
|
||
|
["distance"] = 1,
|
||
|
}
|
||
|
|
||
|
local states_squad_1 = {
|
||
|
["combat"] = 1,
|
||
|
["move"] = 2,
|
||
|
["stealth"] = 2,
|
||
|
["loot"] = 1,
|
||
|
["distance"] = 1,
|
||
|
}
|
||
|
|
||
|
local states_squad_2 = {
|
||
|
["combat"] = 1,
|
||
|
["move"] = 2,
|
||
|
["stealth"] = 2,
|
||
|
["loot"] = 1,
|
||
|
["distance"] = 1,
|
||
|
}
|
||
|
|
||
|
local states_squad_3 = {
|
||
|
["combat"] = 1,
|
||
|
["move"] = 2,
|
||
|
["stealth"] = 2,
|
||
|
["loot"] = 1,
|
||
|
["distance"] = 1,
|
||
|
}
|
||
|
|
||
|
local _current_states = {
|
||
|
["squad_0"] = states_squad_0,
|
||
|
["squad_1"] = states_squad_1,
|
||
|
["squad_2"] = states_squad_2,
|
||
|
["squad_3"] = states_squad_3,
|
||
|
}
|
||
|
|
||
|
local _draw_part
|
||
|
local pos_by_id = {}
|
||
|
|
||
|
------------------------------------------
|
||
|
-- Localized Functions
|
||
|
------------------------------------------
|
||
|
local function on_fighting_actor(npc)
|
||
|
if not (IsStalker(npc)) then
|
||
|
return
|
||
|
end
|
||
|
--utils_data.debug_write(strformat("axr_companions.on_fighting_actor"))
|
||
|
|
||
|
local sim = alife()
|
||
|
for id,squad in pairs(companion_squads) do
|
||
|
xr_combat_ignore.safe_zone_npcs[id] = nil
|
||
|
if (squad and squad.commander_id) then
|
||
|
for k in squad:squad_members() do
|
||
|
local member = db.storage[k.id] and db.storage[k.id].object
|
||
|
if (member and member:alive()) then
|
||
|
member:set_relation(game_object.enemy,npc)
|
||
|
npc:set_relation(game_object.enemy,member)
|
||
|
|
||
|
--[[ Force danger when actor is shot on non-passive mode
|
||
|
if not (member:best_enemy() and sim:has_info(member:id(),"npcx_beh_ignore_actor_enemies")) then
|
||
|
xr_danger.set_script_danger(member,7000,npc:id(),vector():set(npc:position()))
|
||
|
printf("-set_script_danger(%s,%s)", member:name(), npc:name())
|
||
|
end
|
||
|
--]]
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function squad_on_npc_death(squad,se_npc)
|
||
|
--utils_data.debug_write(strformat("axr_companions.squad_on_npc_death"))
|
||
|
non_task_companions[se_npc.id] = nil
|
||
|
if (squad:npc_count() == 0) then
|
||
|
companion_squads[squad.id] = nil
|
||
|
end
|
||
|
-- squad update
|
||
|
if (hg_companion) then hg_companion:hg_squad_update() end
|
||
|
end
|
||
|
|
||
|
local function squad_on_unregister(squad, type_name)
|
||
|
if (type_name ~= "sim_squad_scripted") then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
companion_squads[squad.id] = nil
|
||
|
-- squad update
|
||
|
if (hg_companion) then hg_companion:hg_squad_update() end
|
||
|
end
|
||
|
|
||
|
local function squad_on_first_update(squad)
|
||
|
if (companion_squads[squad.id] ~= nil) then
|
||
|
companion_squads[squad.id] = squad
|
||
|
--squad:set_squad_relation("friend")
|
||
|
squad.scripted_target = "actor"
|
||
|
end
|
||
|
|
||
|
-- backward save compatiblity. remove later
|
||
|
for k in squad:squad_members() do
|
||
|
if (k.id and k.object and se_load_var(k.id,k.object:name(),"companion")) then
|
||
|
if not (se_load_var(k.id,k.object:name(),"companion_hg")) then
|
||
|
se_save_var(k.id,k.object:name(),"companion_hg",1)
|
||
|
end
|
||
|
se_save_var(k.id,k.object:name(), "campfire_step", nil)
|
||
|
se_save_var(k.id,k.object:name(), "campfire", nil)
|
||
|
se_save_var(k.id,k.object:name(), "cover", nil)
|
||
|
companion_squads[squad.id] = squad
|
||
|
squad.scripted_target = "actor"
|
||
|
end
|
||
|
end
|
||
|
-- squad update
|
||
|
if (hg_companion) then hg_companion:hg_squad_update() end
|
||
|
end
|
||
|
|
||
|
local function squad_on_update(squad)
|
||
|
--utils_data.debug_write(strformat("axr_companions.squad_on_update - heli_enemy_flag"))
|
||
|
if (companion_squads[squad.id]) then
|
||
|
squad.scripted_target = "actor"
|
||
|
for k in squad:squad_members() do
|
||
|
if (db.storage[k.id]) then
|
||
|
db.storage[k.id].heli_enemy_flag = load_var(db.actor,"heli_enemy_flag")
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
-- squad update
|
||
|
if (hg_companion) then hg_companion:hg_squad_update() end
|
||
|
end
|
||
|
|
||
|
local function on_level_changing()
|
||
|
local sim = alife()
|
||
|
local se_actor = sim:actor()
|
||
|
|
||
|
local gg = game_graph()
|
||
|
local gvid = se_actor.m_game_vertex_id
|
||
|
local vert = gg:vertex(gvid)
|
||
|
local lvid = vert:level_vertex_id()
|
||
|
local pos = vert:level_point()
|
||
|
|
||
|
for id,v in pairs(companion_squads) do
|
||
|
local squad = sim:object(id)
|
||
|
if (squad and squad.commander_id) then
|
||
|
if companion_squad_can_teleport(squad) then
|
||
|
TeleportSquad(squad,pos,lvid,gvid)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function save_state(m_data)
|
||
|
m_data.non_task_companions = non_task_companions
|
||
|
m_data.companion_squads = {}
|
||
|
for id,squad in pairs(companion_squads) do
|
||
|
if (squad and squad.commander_id) then
|
||
|
m_data.companion_squads[id] = false
|
||
|
end
|
||
|
end
|
||
|
|
||
|
m_data.companion_assigned_items = assigned_items
|
||
|
m_data.companion_current_states_hg = _current_states
|
||
|
end
|
||
|
|
||
|
local function load_state(m_data)
|
||
|
non_task_companions = m_data.non_task_companions or non_task_companions
|
||
|
m_data.non_task_companions = nil
|
||
|
if (m_data.companion_squads) then
|
||
|
companion_squads = m_data.companion_squads or companion_squads
|
||
|
for id,bool in pairs(m_data.companion_squads) do
|
||
|
companion_squads[id] = false
|
||
|
end
|
||
|
end
|
||
|
m_data.companion_squads = nil
|
||
|
|
||
|
assigned_items = m_data.companion_assigned_items or assigned_items
|
||
|
_current_states = m_data.companion_current_states_hg or _current_states
|
||
|
end
|
||
|
|
||
|
local function healing_timer() -- auto-healing every x seconds
|
||
|
ResetTimeEvent("cycle","companion_healing",10)
|
||
|
|
||
|
for id,squad in pairs(companion_squads) do
|
||
|
if (squad and squad.commander_id) then
|
||
|
for k in squad:squad_members() do
|
||
|
st = db.storage[k.id]
|
||
|
local member = st and st.object
|
||
|
if (member and member:alive()) then
|
||
|
local health = member.health
|
||
|
if (health < 1) then
|
||
|
health = clamp(health + 0.1, 0, 1)
|
||
|
member.health = health
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
local function unstuck()
|
||
|
-- Tonex: this is a dumb yet effective hack to unstuck companions that got stuck randomly, by teleporting them into their position
|
||
|
|
||
|
ResetTimeEvent("cycle","companion_unstuck",30)
|
||
|
|
||
|
local sim = alife()
|
||
|
for id,squad in pairs(companion_squads) do
|
||
|
if (squad and squad.commander_id) then
|
||
|
for k in squad:squad_members() do
|
||
|
local npc = db.storage[k.id] and db.storage[k.id].object
|
||
|
if (npc and npc:alive()) then
|
||
|
|
||
|
local pos = npc:position()
|
||
|
local gvid = npc:game_vertex_id()
|
||
|
local lvid = npc:level_vertex_id()
|
||
|
|
||
|
local is_following = npc:dont_has_info("npcx_beh_wait") and true or false
|
||
|
local is_catching_up = (state_mgr.get_state(npc) == "panic")
|
||
|
local is_peaceful = (not npc:best_enemy()) and npc:dont_has_info("npcx_beh_ignore_combat") and true or false
|
||
|
local is_standing = pos_by_id[k.id] and (pos_by_id[k.id]:distance_to(pos) < 1) and true or false
|
||
|
local is_far = (db.actor:position():distance_to(pos) > 10) and true or false
|
||
|
|
||
|
local is_works = npc:dont_has_info("npcx_beh_relax") and true or false
|
||
|
|
||
|
pos_by_id[k.id] = pos
|
||
|
|
||
|
--printf("~Companion (%s): is_following: %s - is_catching_up: %s - is_peaceful: %s - is_standing: %s - is_far: %s", npc:name(), is_following, is_catching_up, is_peaceful, is_standing, is_far)
|
||
|
if is_following and is_catching_up and is_peaceful and is_standing and is_far and is_works then
|
||
|
--printf("-Companion (%s): teleport to unstuck!", npc:name())
|
||
|
|
||
|
-- Teleport in place
|
||
|
if (db.offline_objects[k.id]) then
|
||
|
db.offline_objects[k.id].level_vertex_id = nil
|
||
|
end
|
||
|
db.spawned_vertex_by_id[k.id] = nil
|
||
|
sim:teleport_object(k.id,gvid,lvid,pos)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
local function on_game_load()
|
||
|
-- Patriarch achievement doubles allowed squad sizes.
|
||
|
local ini = ini_file("ai_tweaks\\axr_companions.ltx")
|
||
|
if (game_achievements.has_achievement("patriarch")) then
|
||
|
max_actor_squad_size = ini:r_float_ex("main","max_actor_squad_size_patriarch") or 4
|
||
|
end
|
||
|
CreateTimeEvent("cycle","companion_healing",10,healing_timer)
|
||
|
CreateTimeEvent("cycle","companion_unstuck",30,unstuck)
|
||
|
|
||
|
-- backward save compatiblity, remove me later!
|
||
|
local c_idx = 1
|
||
|
local cid = load_var(db.actor,"companion_1")
|
||
|
while (cid ~= nil) do
|
||
|
local se_obj = alife_object(cid)
|
||
|
if (se_obj) then
|
||
|
non_task_companions[se_obj.id] = true
|
||
|
local squad = se_obj.group_id and se_obj.group_id ~= 65535 and alife_object(se_obj.group_id)
|
||
|
if (squad) then
|
||
|
companion_squads[squad.id] = squad
|
||
|
end
|
||
|
end
|
||
|
save_var(db.actor,"companion_"..c_idx,nil)
|
||
|
c_idx = c_idx + 1
|
||
|
cid = load_var(db.actor,"companion_"..c_idx)
|
||
|
printf("! axr_companions | cleared old data: %s", "companion_"..c_idx)
|
||
|
end
|
||
|
|
||
|
-- squad update
|
||
|
if (hg_companion) then hg_companion:hg_squad_update() end
|
||
|
end
|
||
|
|
||
|
---------------------------------
|
||
|
-- Register Callbacks
|
||
|
---------------------------------
|
||
|
function on_game_start()
|
||
|
local ini = ini_file("ai_tweaks\\axr_companions.ltx")
|
||
|
Enabled = ini:r_bool_ex("main","enable",false)
|
||
|
if not (Enabled) then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local function actor_on_first_update()
|
||
|
activate_hud()
|
||
|
end
|
||
|
RegisterScriptCallback("npc_on_fighting_actor",on_fighting_actor)
|
||
|
RegisterScriptCallback("squad_on_update",squad_on_update)
|
||
|
RegisterScriptCallback("ActorMenu_on_item_before_move",OnItemBeforeMove)
|
||
|
RegisterScriptCallback("ActorMenu_on_item_after_move",OnItemAfterMove)
|
||
|
RegisterScriptCallback("on_key_release",on_key_release)
|
||
|
|
||
|
RegisterScriptCallback("actor_on_first_update",actor_on_first_update)
|
||
|
RegisterScriptCallback("squad_on_first_update",squad_on_first_update)
|
||
|
RegisterScriptCallback("squad_on_npc_death",squad_on_npc_death)
|
||
|
RegisterScriptCallback("server_entity_on_unregister",squad_on_unregister)
|
||
|
RegisterScriptCallback("on_level_changing",on_level_changing)
|
||
|
RegisterScriptCallback("save_state",save_state)
|
||
|
RegisterScriptCallback("load_state",load_state)
|
||
|
RegisterScriptCallback("on_game_load",on_game_load)
|
||
|
|
||
|
allow_only_friends_as_companions = ini:r_bool_ex("main","allow_only_friends_as_companions",false)
|
||
|
allow_simulation_squads_as_companions = ini:r_bool_ex("main","allow_simulation_squads_as_companions",false)
|
||
|
max_actor_squad_size = ini:r_float_ex("main","max_actor_squad_size") or 2
|
||
|
end
|
||
|
|
||
|
---------------------------------
|
||
|
-- Management
|
||
|
---------------------------------
|
||
|
function companion_squad_can_teleport(squad)
|
||
|
local id = squad:commander_id()
|
||
|
local sim = alife()
|
||
|
local se_obj = sim:object(id)
|
||
|
if (se_obj) then
|
||
|
if (se_load_var(se_obj.id,se_obj:name(),"companion_cannot_teleport")) then
|
||
|
return false
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if (id) and (sim:has_info(id,"npcx_beh_patrol_mode") or sim:has_info(id,"npcx_beh_wait")) then
|
||
|
return false
|
||
|
end
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
-- doesn't include task companions
|
||
|
function get_companion_count()
|
||
|
return size_table(non_task_companions)
|
||
|
end
|
||
|
|
||
|
function can_join_actor(npc)
|
||
|
local squad = get_object_squad(npc)
|
||
|
if squad ~= nil and squad:commander_id() ~= npc:id() then
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
if (get_companion_count() + utils_obj.get_squad_count(npc) <= max_actor_squad_size) then
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
function list_actor_squad_by_id()
|
||
|
local t = {}
|
||
|
local size_t = 0
|
||
|
for id,squad in spairs(companion_squads, function(t,a,b) return a < b end) do
|
||
|
if (squad and squad.commander_id) then
|
||
|
if not (axr_task_manager.hostages_by_id[squad:commander_id()]) then
|
||
|
for k in squad:squad_members() do
|
||
|
--printf("member %s",k.id)
|
||
|
size_t = size_t + 1
|
||
|
t[size_t] = k.id
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
return t
|
||
|
end
|
||
|
|
||
|
function setup_companion_logic(npc,st,loaded,cannot_dismiss)
|
||
|
npc:give_info_portion("npcx_is_companion")
|
||
|
|
||
|
if (cannot_dismiss or se_load_var(npc:id(),npc:name(),"companion_cannot_dismiss")) then
|
||
|
npc:give_info_portion("npcx_beh_cannot_dismiss")
|
||
|
end
|
||
|
|
||
|
local ltx_name = "scripts\\beh_companion.ltx"
|
||
|
local ltx = ini_file(ltx_name)
|
||
|
if not (ltx) then
|
||
|
log("ERROR: do not have access to scripts\\beh_companion.ltx! Make sure you installed properly!")
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local id = npc:id()
|
||
|
local sim = alife()
|
||
|
local se_npc = sim:object(id)
|
||
|
local unreg_id = se_npc and se_npc.m_smart_terrain_id
|
||
|
if (unreg_id and unreg_id ~= 65535) then
|
||
|
local unreg = sim:object(unreg_id)
|
||
|
if (unreg) then
|
||
|
unreg:unregister_npc(se_npc)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
xr_logic.configure_schemes(npc, ltx, ltx_name, modules.stype_stalker, loaded and st.loaded_section_logic or "logic", "")
|
||
|
|
||
|
local section = loaded and st.loaded_active_section or xr_logic.determine_section_to_activate(npc, ltx, "logic", db.actor)
|
||
|
xr_logic.activate_by_section(npc, ltx, section, "", loaded)
|
||
|
--printf("setup complete")
|
||
|
end
|
||
|
|
||
|
function add_special_task_npc_to_actor_squad(npc)
|
||
|
local id = npc:id()
|
||
|
local sim = alife()
|
||
|
local se_npc = sim:object(id)
|
||
|
local unreg_id = se_npc and se_npc.m_smart_terrain_id
|
||
|
if (unreg_id and unreg_id ~= 65535) then
|
||
|
local unreg = sim:object(unreg_id)
|
||
|
if (unreg) then
|
||
|
unreg:unregister_npc(se_npc)
|
||
|
end
|
||
|
end
|
||
|
npc:inactualize_patrol_path()
|
||
|
npc:give_info_portion("npcx_is_companion")
|
||
|
xr_logic.set_new_scheme_and_logic(npc,"beh","beh@base","logic",nil,"scripts\\beh_companion.ltx")
|
||
|
setup_companion_logic(npc)
|
||
|
end
|
||
|
|
||
|
function add_to_actor_squad(npc)
|
||
|
local id = npc:id()
|
||
|
non_task_companions[id] = true
|
||
|
se_save_var(id,npc:name(),"companion",true)
|
||
|
se_save_var(id,npc:name(),"companion_hg",1)
|
||
|
npc:inactualize_patrol_path()
|
||
|
setup_companion_logic(npc,db.storage[id],false)
|
||
|
|
||
|
cycle_companions_move_mode(_current_states["squad_1"]["move"])
|
||
|
cycle_companions_stealth_mode(_current_states["squad_1"]["stealth"])
|
||
|
cycle_companions_loot_mode(_current_states["squad_1"]["loot"])
|
||
|
cycle_companions_combat_mode(_current_states["squad_1"]["combat"])
|
||
|
|
||
|
-- squad update
|
||
|
if (hg_companion) then hg_companion:hg_squad_update() end
|
||
|
end
|
||
|
|
||
|
function remove_from_actor_squad(npc)
|
||
|
local squad = get_object_squad(npc)
|
||
|
if (squad) then
|
||
|
squad.scripted_target = nil
|
||
|
companion_squads[squad.id] = nil
|
||
|
if _G.WARFARE then
|
||
|
sim_squad_warfare.set_target(squad, smart_terrain.nearest_to_actor_smart.id)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
stalker_generic.remove_level_spot(npc:id())
|
||
|
non_task_companions[npc:id()] = nil
|
||
|
npc:disable_info_portion("npcx_is_companion")
|
||
|
npc:disable_info_portion("npcx_beh_cannot_dismiss")
|
||
|
xr_logic.restore_scheme_and_logic(npc)
|
||
|
|
||
|
local se_obj = alife_object(npc:id())
|
||
|
if not (se_obj) then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
se_save_var(se_obj.id,se_obj:name(),"companion",nil)
|
||
|
se_save_var(se_obj.id,se_obj:name(),"companion_cannot_dismiss",nil)
|
||
|
se_save_var(se_obj.id,se_obj:name(),"companion_cannot_teleport",nil)
|
||
|
se_save_var(se_obj.id,se_obj:name(),"companion_hg",nil)
|
||
|
|
||
|
-- squad update
|
||
|
if (hg_companion) then hg_companion:hg_squad_update() end
|
||
|
end
|
||
|
|
||
|
function remove_all_from_actor_squad()
|
||
|
local sq_d = {}
|
||
|
for id,squad in pairs(companion_squads) do
|
||
|
if (squad) then
|
||
|
sq_d[#sq_d + 1] = squad
|
||
|
for k in squad:squad_members() do
|
||
|
local se_obj = k.object or alife_object(k.id)
|
||
|
stalker_generic.remove_level_spot(se_obj.id)
|
||
|
utils_obj.execute_script_on_squad(se_obj,remove_from_actor_squad) -- A fix to companions stacking in Relations tab
|
||
|
end
|
||
|
end
|
||
|
companion_squads[id] = nil
|
||
|
end
|
||
|
|
||
|
for id,b in pairs(non_task_companions) do
|
||
|
stalker_generic.remove_level_spot(id)
|
||
|
non_task_companions[id] = nil
|
||
|
end
|
||
|
|
||
|
return sq_d
|
||
|
end
|
||
|
|
||
|
-- Tronex
|
||
|
local fade_trigger = 0
|
||
|
local fade_tbl = {}
|
||
|
local function releaser()
|
||
|
local function fade_in_out()
|
||
|
for i=1,#fade_tbl do
|
||
|
fade_tbl[i]:remove_squad() -- Release
|
||
|
end
|
||
|
empty_table(fade_tbl)
|
||
|
fade_trigger = 0
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
if (#fade_tbl > 0) and ((time_global() - fade_trigger) > 4000) then
|
||
|
level.add_pp_effector("fade_in_out.ppe", 1313, false) -- Play effect
|
||
|
CreateTimeEvent(0,"release_companion",4,fade_in_out)
|
||
|
fade_trigger = time_global()
|
||
|
else
|
||
|
printf("~ Companions | Can't play the effect when it's being played aleady")
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function stop_talking(squad)
|
||
|
for k in squad:squad_members() do
|
||
|
local npc = db.storage[k.id] and db.storage[k.id].object
|
||
|
if npc and npc:is_talking() then
|
||
|
db.actor:stop_talk()
|
||
|
npc:stop_talk()
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function add_special_squad(squad, special, cannot_teleport)
|
||
|
cannot_teleport = cannot_teleport or false
|
||
|
if not (squad) then
|
||
|
printf("~ Companions | squad can't be added cause it doesn't exist?!")
|
||
|
return
|
||
|
end
|
||
|
|
||
|
if companion_squads[squad.id] then
|
||
|
printf("~ Companions | squad (%s) is already companion squad", squad.id)
|
||
|
return
|
||
|
end
|
||
|
|
||
|
companion_squads[squad.id] = squad
|
||
|
for k in squad:squad_members() do
|
||
|
local npc = k.id and (db.storage[k.id] and db.storage[k.id].object or level.object_by_id(k.id))
|
||
|
if (npc) then
|
||
|
se_save_var(k.id,k.object:name(),"companion",true)
|
||
|
se_save_var(k.id,k.object:name(),"companion_cannot_dismiss",true)
|
||
|
se_save_var(k.id,k.object:name(),"companion_cannot_teleport",cannot_teleport)
|
||
|
se_save_var(k.id,k.object:name(),"companion_hg",1)
|
||
|
|
||
|
if special then
|
||
|
local se_obj = alife_object(k.id)
|
||
|
if (se_obj) then
|
||
|
SIMBOARD:setup_squad_and_group(se_obj)
|
||
|
end
|
||
|
else
|
||
|
setup_companion_logic(npc,db.storage[k.id],false,true)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- squad update
|
||
|
if (hg_companion) then hg_companion:hg_squad_update() end
|
||
|
end
|
||
|
|
||
|
function dismiss_special_squad(squad)
|
||
|
if not (squad) then
|
||
|
printf("~ Companions | squad can't be dismissed cause it doesn't exist?!")
|
||
|
return
|
||
|
end
|
||
|
|
||
|
if companion_squads[squad.id] == nil then
|
||
|
printf("~ Companions | squad (%s) can't be dismissed cause it's not a companion squad", squad.id)
|
||
|
return
|
||
|
end
|
||
|
|
||
|
|
||
|
for k in squad:squad_members() do
|
||
|
local se_obj = k.object or alife_object(k.id)
|
||
|
utils_obj.execute_script_on_squad(se_obj,remove_from_actor_squad) -- A fix to companions stacking in Relations tab
|
||
|
end
|
||
|
companion_squads[squad.id] = nil -- remove from comp table
|
||
|
end
|
||
|
|
||
|
function release_special_squad(squad)
|
||
|
if not (squad) then
|
||
|
printf("~ Companions | squad can't be released cause it doesn't exist?!")
|
||
|
return
|
||
|
end
|
||
|
|
||
|
-- Dismiss
|
||
|
dismiss_special_squad(squad)
|
||
|
|
||
|
-- Stop talking
|
||
|
stop_talking(squad)
|
||
|
|
||
|
-- Release after fade out
|
||
|
fade_tbl[#fade_tbl + 1] = squad
|
||
|
releaser()
|
||
|
end
|
||
|
|
||
|
function release_all_squads()
|
||
|
-- Dismiss all
|
||
|
local sq_r = remove_all_from_actor_squad() or {}
|
||
|
|
||
|
-- Stop talking + Release after fade out
|
||
|
for i=1,#sq_r do
|
||
|
stop_talking(sq_r[i])
|
||
|
fade_tbl[#fade_tbl + 1] = sq_r[i]
|
||
|
end
|
||
|
releaser()
|
||
|
end
|
||
|
|
||
|
function epic_hack()
|
||
|
local companion = level.object_by_id(companion_id)
|
||
|
if companion and companion:alive() and (not companion:best_enemy()) then
|
||
|
for _,id in ipairs(db.heli_enemies) do
|
||
|
local target = level.object_by_id(id)
|
||
|
if target
|
||
|
and target:alive()
|
||
|
and target:best_enemy()
|
||
|
and ((target:best_enemy():id() == 0) or (target:best_enemy():id() == companion_id))
|
||
|
then
|
||
|
xr_danger.set_script_danger(companion,7000,target:id(),vector():set(target:position()))
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
---------------------------------
|
||
|
-- Tasks
|
||
|
---------------------------------
|
||
|
|
||
|
xr_effects.remove_special_companion_squad = function(actor,npc,p)
|
||
|
for i=1,#p do
|
||
|
local squad = get_story_squad(p[i])
|
||
|
release_special_squad(squad)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
xr_effects.dismiss_special_companion_squad = function(actor,npc,p)
|
||
|
for i=1,#p do
|
||
|
local squad = get_story_squad(p[i])
|
||
|
dismiss_special_squad(squad)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
xr_effects.remove_all_special_companion = function()
|
||
|
release_all_squads()
|
||
|
end
|
||
|
|
||
|
xr_effects.setup_companion_special_task = function(actor,npc,p)
|
||
|
level.add_pp_effector("black.ppe", 1500, false)
|
||
|
xr_effects.setup_companion_task(actor,npc,p)
|
||
|
end
|
||
|
|
||
|
-- remove a companion squad by story id
|
||
|
xr_effects.remove_task_companion = function(actor,npc,p)
|
||
|
local squad = p[1] and get_story_squad(p[1])
|
||
|
dismiss_special_squad(squad)
|
||
|
end
|
||
|
|
||
|
xr_effects.add_task_companion = function(actor,npc,p)
|
||
|
local squad = p[1] and get_story_squad(p[1])
|
||
|
local special = p[2] == "true" and true or false
|
||
|
local cannot_teleport = p[3] == "true" and true or false
|
||
|
if squad then
|
||
|
add_special_squad(squad, special, cannot_teleport)
|
||
|
else
|
||
|
printe("! ERROR add_task_companion | can't find story squad [%s]", p[1])
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Task giver squad
|
||
|
xr_effects.add_task_giver_companion = function(actor,npc,p)
|
||
|
if (not p[1]) then
|
||
|
printe("! ERROR add_task_giver_companion | can't set up task giver as companion because task is not declared")
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local tm = task_manager.get_task_manager()
|
||
|
local tsk = tm.task_info[p[1]]
|
||
|
if (not tsk) then
|
||
|
printe("! ERROR add_task_giver_companion | task [%s] is not active", p[1])
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local id = tsk.task_giver_id
|
||
|
if (not id) then
|
||
|
printe("! ERROR add_task_giver_companion | task giver id not found for task [%s]", p[1])
|
||
|
return
|
||
|
end
|
||
|
|
||
|
|
||
|
local npc = db.storage[id] and db.storage[id].object or level.object_by_id(id)
|
||
|
if (not npc) then
|
||
|
printe("! ERROR add_task_giver_companion | can't find task giver object (%s) for task [%s]", id, p[1])
|
||
|
end
|
||
|
|
||
|
local squad = get_object_squad(npc)
|
||
|
add_special_squad(squad)
|
||
|
end
|
||
|
|
||
|
xr_effects.dismiss_task_giver_companion = function(actor,npc,p)
|
||
|
if (not p[1]) then
|
||
|
printe("! ERROR add_task_giver_companion | can't set up task giver as companion because task is not declared")
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local tm = task_manager.get_task_manager()
|
||
|
local tsk = tm.task_info[p[1]]
|
||
|
if (not tsk) then
|
||
|
printe("! ERROR add_task_giver_companion | task [%s] is not active", p[1])
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local id = tsk.task_giver_id
|
||
|
if (not id) then
|
||
|
printe("! ERROR add_task_giver_companion | task giver id not found for task [%s]", p[1])
|
||
|
return
|
||
|
end
|
||
|
|
||
|
|
||
|
local npc = db.storage[id] and db.storage[id].object or level.object_by_id(id)
|
||
|
if (not npc) then
|
||
|
printe("! ERROR add_task_giver_companion | can't find task giver object (%s) for task [%s]", id, p[1])
|
||
|
end
|
||
|
|
||
|
local squad = get_object_squad(npc)
|
||
|
dismiss_special_squad(squad)
|
||
|
end
|
||
|
|
||
|
-- Speaker squad
|
||
|
xr_effects.add_speaker_companion = function(actor,npc,p)
|
||
|
|
||
|
if (not db.actor:is_talking()) then
|
||
|
printe("! ERROR add_speaker_companion | actor is not talking")
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local task_id = p[1]
|
||
|
local speaker = mob_trade.GetTalkingNpc()
|
||
|
local id = speaker:id()
|
||
|
if (not id) then
|
||
|
printe("! ERROR add_speaker_companion | speaker id not found for task [%s]", task_id)
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local npc = db.storage[id] and db.storage[id].object or level.object_by_id(id)
|
||
|
if (not npc) then
|
||
|
printe("! ERROR add_speaker_companion | can't find speaker object (%s) for task [%s]", id, task_id)
|
||
|
end
|
||
|
|
||
|
local squad = get_object_squad(npc)
|
||
|
add_special_squad(squad)
|
||
|
|
||
|
local var = load_var(db.actor, task_id)
|
||
|
if var and task_id then
|
||
|
var.companion_squad_id = squad.id
|
||
|
save_var(db.actor, task_id, var)
|
||
|
printf("- %s | saved companion id (%s)", task_id, id)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
xr_effects.dismiss_speaker_companion = function(actor,npc,p)
|
||
|
|
||
|
local task_id = p[1]
|
||
|
local var = task_id and load_var(db.actor, task_id)
|
||
|
local sq_id = var and var.companion_squad_id
|
||
|
|
||
|
if (not sq_id) and db.actor:is_talking() then
|
||
|
local speaker = mob_trade.GetTalkingNpc()
|
||
|
local se_speaker = alife_object(speaker:id())
|
||
|
if se_speaker then
|
||
|
sq_id = se_speaker.group_id
|
||
|
end
|
||
|
end
|
||
|
if (not sq_id) then
|
||
|
printe("! ERROR dismiss_speaker_companion | speaker squad id not found for task [%s]", task_id)
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local squad = alife_object(sq_id)
|
||
|
dismiss_special_squad(squad)
|
||
|
end
|
||
|
|
||
|
|
||
|
xr_effects.setup_companion_task = function(actor,npc,p)
|
||
|
-- Spawn a squad that will become actor companion for special tasks
|
||
|
-- param 1 - squad section
|
||
|
-- param 2 - smart
|
||
|
-- param 3 - variable name. Will use smart_id in a pstor variable instead
|
||
|
-- param 4 - disable level transition for the squad
|
||
|
-- param 5 - is a hostage
|
||
|
|
||
|
local sq_sec = p[1]
|
||
|
local smrt_name = p[2]
|
||
|
local var_id = p[3]
|
||
|
local cant_teleport = p[4]
|
||
|
local is_hostage = p[5]
|
||
|
|
||
|
local var = var_id and var_id ~= "nil" and load_var(db.actor,var_id)
|
||
|
local smart_id = var and var.smart_id
|
||
|
local smart = smart_id and alife_object(smart_id) or smrt_name and smrt_name ~= "nil" and SIMBOARD.smarts_by_names[smrt_name]
|
||
|
|
||
|
--xQd
|
||
|
if _G.WARFARE and (not smrt_name or smrt_name == "false") and (not is_hostage or is_hostage == "false") then -- sim warfare escort
|
||
|
--printf("no smart terrain found for companion task")
|
||
|
for sname,sm in pairs(SIMBOARD.smarts_by_names) do
|
||
|
if sm.is_on_actor_level == true and sm.dist_to_actor <= 50 then
|
||
|
smart = sm
|
||
|
save_var(db.actor,"warfare_escort_task_smart",sname) -- store the name of the smart terrain where the escorted spawn
|
||
|
--printf("found smart to spawn companion task %s", sname)
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
--xQd end
|
||
|
|
||
|
if not (smart) then
|
||
|
printe("! setup_companion_task: no smart found! %s", smart_id or smrt_name)
|
||
|
return
|
||
|
end
|
||
|
|
||
|
if not (ini_sys:section_exist(sq_sec)) then
|
||
|
printe("! setup_companion_task: Trying to setup companion squad with a non-existent section!")
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local squad = alife_create(sq_sec,smart.position,smart.m_level_vertex_id,smart.m_game_vertex_id)
|
||
|
|
||
|
-- Setup relation
|
||
|
squad:create_npc(smart)
|
||
|
squad:set_squad_relation()
|
||
|
|
||
|
-- Setup logic
|
||
|
companion_squads[squad.id] = squad
|
||
|
for k in squad:squad_members() do
|
||
|
local se_obj = k.object or k.id and sim:object(k.id)
|
||
|
if (se_obj) then
|
||
|
se_save_var(se_obj.id,se_obj:name(),"companion",true)
|
||
|
se_save_var(se_obj.id,se_obj:name(),"companion_cannot_dismiss",true)
|
||
|
se_save_var(se_obj.id,se_obj:name(),"companion_cannot_teleport",cant_teleport == "true")
|
||
|
se_save_var(se_obj.id,se_obj:name(),"companion_hg",1)
|
||
|
|
||
|
SIMBOARD:setup_squad_and_group(se_obj)
|
||
|
if (is_hostage == "true") then
|
||
|
axr_task_manager.hostages_by_id[se_obj.id] = smart.id
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- squad update
|
||
|
if (hg_companion) then hg_companion:hg_squad_update() end
|
||
|
|
||
|
--[[ Setup logic
|
||
|
--companion_squads[squad.id] = squad
|
||
|
add_special_squad(squad, true, cant_teleport == "true")
|
||
|
if (is_hostage == "true") then
|
||
|
for k in squad:squad_members() do
|
||
|
local se_obj = k.object or k.id and alife_object(k.id)
|
||
|
if (se_obj) then
|
||
|
axr_task_manager.hostages_by_id[se_obj.id] = smart.id
|
||
|
end
|
||
|
end
|
||
|
save_var( db.actor, "drx_sl_hostage_giver_needed", true )
|
||
|
end
|
||
|
--]]
|
||
|
|
||
|
-- Add to ignore offline combat simulation list
|
||
|
sim_offline_combat.task_squads[squad.id] = true
|
||
|
|
||
|
--save_var(db.actor,var_id or "task_companion_slot_1",squad.id)
|
||
|
--CreateTimeEvent(0,"add_special_task_squad",5,add_special_task_squad,squad.id,p[4] == "true")
|
||
|
end
|
||
|
|
||
|
---------------------------------
|
||
|
-- Companion interactions
|
||
|
---------------------------------
|
||
|
|
||
|
function set_companion_allow_teleport(npc)
|
||
|
local se_obj = alife_object(npc:id())
|
||
|
if not (se_obj) then
|
||
|
return
|
||
|
end
|
||
|
se_save_var(se_obj.id,se_obj:name(),"companion_cannot_teleport",nil)
|
||
|
end
|
||
|
|
||
|
function set_companion_disable_teleport(npc)
|
||
|
local se_obj = alife_object(npc:id())
|
||
|
if not (se_obj) then
|
||
|
return
|
||
|
end
|
||
|
se_save_var(se_obj.id,se_obj:name(),"companion_cannot_teleport",true)
|
||
|
end
|
||
|
|
||
|
function set_companion_combat_type_camper(npc)
|
||
|
npc:disable_info_portion("npcx_beh_combat_tactics_monolith")
|
||
|
npc:give_info_portion("npcx_beh_combat_tactics_camper")
|
||
|
end
|
||
|
|
||
|
function set_companion_combat_type_monolith(npc)
|
||
|
npc:disable_info_portion("npcx_beh_combat_tactics_camper")
|
||
|
npc:give_info_portion("npcx_beh_combat_tactics_monolith")
|
||
|
end
|
||
|
|
||
|
function set_companion_combat_type_default(npc)
|
||
|
npc:disable_info_portion("npcx_beh_combat_tactics_monolith")
|
||
|
npc:disable_info_portion("npcx_beh_combat_tactics_camper")
|
||
|
end
|
||
|
|
||
|
function set_companion_hide_in_cover(npc)
|
||
|
npc:give_info_portion("npcx_beh_hide_in_cover")
|
||
|
npc:disable_info_portion("npcx_beh_wait")
|
||
|
end
|
||
|
|
||
|
function set_companion_to_wait_state(npc)
|
||
|
npc:give_info_portion("npcx_beh_wait")
|
||
|
save_var(npc,"fight_from_point",npc:level_vertex_id())
|
||
|
end
|
||
|
|
||
|
function set_companion_to_patrol_state(npc)
|
||
|
npc:give_info_portion("npcx_beh_patrol_mode")
|
||
|
end
|
||
|
|
||
|
function set_companion_to_follow_state(npc)
|
||
|
npc:disable_info_portion("npcx_beh_wait")
|
||
|
npc:disable_info_portion("npcx_beh_hide_in_cover")
|
||
|
npc:disable_info_portion("npcx_beh_patrol_mode")
|
||
|
save_var(npc,"fight_from_point",nil)
|
||
|
end
|
||
|
|
||
|
function set_companion_to_attack_state(npc)
|
||
|
npc:disable_info_portion("npcx_beh_ignore_combat")
|
||
|
npc:disable_info_portion("npcx_beh_ignore_actor_enemies")
|
||
|
end
|
||
|
|
||
|
function set_companion_to_ignore_combat_state(npc)
|
||
|
npc:give_info_portion("npcx_beh_ignore_combat")
|
||
|
npc:give_info_portion("npcx_beh_ignore_actor_enemies")
|
||
|
end
|
||
|
|
||
|
function set_companion_to_attack_only_actor_combat_enemy_state(npc)
|
||
|
npc:give_info_portion("npcx_beh_ignore_combat")
|
||
|
npc:disable_info_portion("npcx_beh_ignore_actor_enemies")
|
||
|
end
|
||
|
|
||
|
function set_companion_to_stealth_substate(npc)
|
||
|
npc:give_info_portion("npcx_beh_substate_stealth")
|
||
|
end
|
||
|
|
||
|
function set_companion_to_relax_substate(npc)
|
||
|
npc:give_info_portion("npcx_beh_substate_relax")
|
||
|
end
|
||
|
|
||
|
function set_companion_to_default_substate(npc)
|
||
|
npc:disable_info_portion("npcx_beh_substate_stealth")
|
||
|
npc:disable_info_portion("npcx_beh_substate_relax")
|
||
|
end
|
||
|
|
||
|
function set_companion_to_loot_items_and_corpses(npc)
|
||
|
npc:give_info_portion("npcx_beh_gather_items")
|
||
|
npc:give_info_portion("npcx_beh_loot_corpses")
|
||
|
end
|
||
|
|
||
|
function set_companion_to_loot_items_only(npc)
|
||
|
npc:give_info_portion("npcx_beh_gather_items")
|
||
|
npc:disable_info_portion("npcx_beh_loot_corpses")
|
||
|
end
|
||
|
|
||
|
function set_companion_to_loot_corpses_only(npc)
|
||
|
npc:disable_info_portion("npcx_beh_gather_items")
|
||
|
npc:give_info_portion("npcx_beh_loot_corpses")
|
||
|
end
|
||
|
|
||
|
function set_companion_to_loot_nothing(npc)
|
||
|
npc:disable_info_portion("npcx_beh_gather_items")
|
||
|
npc:disable_info_portion("npcx_beh_loot_corpses")
|
||
|
end
|
||
|
|
||
|
function set_companion_to_stay_close(npc)
|
||
|
npc:disable_info_portion("npcx_beh_distance_far")
|
||
|
end
|
||
|
|
||
|
function set_companion_to_stay_far(npc)
|
||
|
npc:give_info_portion("npcx_beh_distance_far")
|
||
|
end
|
||
|
|
||
|
-- Switch
|
||
|
function switch_companion_distance(npc)
|
||
|
if ( npc:has_info("npcx_beh_distance_far") ) then
|
||
|
npc:disable_info_portion("npcx_beh_distance_far")
|
||
|
else
|
||
|
npc:give_info_portion("npcx_beh_distance_far")
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function switch_companion_patrol_mode(npc)
|
||
|
if ( npc:has_info("npcx_beh_patrol_mode") ) then
|
||
|
npc:disable_info_portion("npcx_beh_patrol_mode")
|
||
|
else
|
||
|
npc:give_info_portion("npcx_beh_patrol_mode")
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function switch_companion_gather_items(npc)
|
||
|
if ( npc:has_info("npcx_beh_gather_items") ) then
|
||
|
actor_menu.set_msg(1, game.translate_string("st_disable_looting"),8)
|
||
|
npc:disable_info_portion("npcx_beh_gather_items")
|
||
|
else
|
||
|
actor_menu.set_msg(1, game.translate_string("st_enable_looting"),8)
|
||
|
npc:give_info_portion("npcx_beh_gather_items")
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function switch_companion_loot_corpses(npc)
|
||
|
if ( npc:has_info("npcx_beh_loot_corpses") ) then
|
||
|
npc:disable_info_portion("npcx_beh_loot_corpses")
|
||
|
else
|
||
|
npc:give_info_portion("npcx_beh_loot_corpses")
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function companion_remove_waypoints(npc)
|
||
|
npc:disable_info_portion("npcx_beh_patrol_mode")
|
||
|
local i = 1
|
||
|
local p = se_load_var(npc:id(),npc:name(),"pathpoint"..i)--load_var(npc,"pathpoint"..tostring(i))
|
||
|
while p do
|
||
|
se_save_var(npc:id(),npc:name(),"pathpoint"..i,nil)--save_var(npc,"pathpoint"..tostring(i),nil)
|
||
|
i = i + 1
|
||
|
p = se_load_var(npc:id(),npc:name(),"pathpoint"..i)--load_var(npc,"pathpoint"..tostring(i))
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function companion_add_waypoints(npc,pos)
|
||
|
local i = 1
|
||
|
local p = se_load_var(npc:id(),npc:name(),"pathpoint"..i) --load_var(npc,"pathpoint"..tostring(i))
|
||
|
while p do
|
||
|
i = i + 1
|
||
|
p = se_load_var(npc:id(),npc:name(),"pathpoint"..i) --load_var(npc,"pathpoint"..tostring(i))
|
||
|
end
|
||
|
local pos = db.actor:position()
|
||
|
local s = "5000,patrol | pos:"..pos.x..","..pos.y..","..pos.z
|
||
|
se_save_var(npc:id(),npc:name(),"pathpoint"..i,s) --save_var(npc,"pathpoint"..tostring(i),s)
|
||
|
end
|
||
|
|
||
|
-- Get mode
|
||
|
function get_companion_combat_mode(npc)
|
||
|
local group = 1
|
||
|
if (hg_companion) then
|
||
|
group = hg_companion:return_group()
|
||
|
end
|
||
|
|
||
|
if (not npc) then
|
||
|
return _current_states["squad_"..group]["combat"]
|
||
|
else
|
||
|
return npc:has_info("npcx_beh_ignore_combat") and 2 or 1
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function get_companion_move_mode(npc)
|
||
|
local group = 1
|
||
|
if (hg_companion) then
|
||
|
group = hg_companion:return_group()
|
||
|
end
|
||
|
|
||
|
if (not npc) then
|
||
|
return _current_states["squad_"..group]["move"]
|
||
|
else
|
||
|
return npc:has_info("npcx_beh_wait") and 1 or 2
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function get_companion_stealth_mode(npc)
|
||
|
local group = 1
|
||
|
if (hg_companion) then
|
||
|
group = hg_companion:return_group()
|
||
|
end
|
||
|
|
||
|
if (not npc) then
|
||
|
return _current_states["squad_"..group]["stealth"]
|
||
|
else
|
||
|
return npc:has_info("npcx_beh_substate_stealth") and 1 or 2
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function get_companion_distance_mode(npc)
|
||
|
local group = 1
|
||
|
if (hg_companion) then
|
||
|
group = hg_companion:return_group()
|
||
|
end
|
||
|
|
||
|
if (not npc) then
|
||
|
return _current_states["squad_"..group]["distance"]
|
||
|
else
|
||
|
return npc:has_info("npcx_beh_distance_far") and 2 or 1
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function get_companion_loot_mode(npc)
|
||
|
local group = 1
|
||
|
if (hg_companion) then
|
||
|
group = hg_companion:return_group()
|
||
|
end
|
||
|
|
||
|
if (not npc) then
|
||
|
return _current_states["squad_"..group]["loot"]
|
||
|
else
|
||
|
return npc:has_info("npcx_beh_gather_items") and 1 or 2
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Individual
|
||
|
function set_companion_squad_move_mode(mode,npc,squad)
|
||
|
local t = {"set_companion_to_wait_state","set_companion_to_follow_state"}
|
||
|
local f = this[t[mode]]
|
||
|
if (f) then
|
||
|
squad = squad or get_object_squad(npc)
|
||
|
if (squad and squad.commander_id) then
|
||
|
if not (axr_task_manager.hostages_by_id[squad:commander_id()]) then
|
||
|
for k in squad:squad_members() do
|
||
|
st = db.storage[k.id]
|
||
|
if (k.id == squad:commander_id()) then
|
||
|
if (st and st.beh) then
|
||
|
st.beh.rally_lvid = nil
|
||
|
end
|
||
|
end
|
||
|
local member = st and st.object
|
||
|
if (member and member:alive()) then
|
||
|
f(member)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function set_companion_squad_combat_mode(mode,npc,squad)
|
||
|
local t = {"set_companion_to_attack_state","set_companion_to_ignore_combat_state","set_companion_to_attack_only_actor_combat_enemy_state"}
|
||
|
local f = this[t[mode]]
|
||
|
if (f) then
|
||
|
squad = squad or get_object_squad(npc)
|
||
|
if (squad and squad.commander_id) then
|
||
|
if not (axr_task_manager.hostages_by_id[squad:commander_id()]) then
|
||
|
for k in squad:squad_members() do
|
||
|
local member = db.storage[k.id] and db.storage[k.id].object
|
||
|
if (member and member:alive()) then
|
||
|
f(member)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function set_companion_squad_stealth_mode(mode,npc,squad)
|
||
|
local t = {"set_companion_to_stealth_substate","set_companion_to_default_substate"}
|
||
|
local f = this[t[mode]]
|
||
|
if (f) then
|
||
|
squad = squad or get_object_squad(npc)
|
||
|
if (squad and squad.commander_id) then
|
||
|
if not (axr_task_manager.hostages_by_id[squad:commander_id()]) then
|
||
|
for k in squad:squad_members() do
|
||
|
local member = db.storage[k.id] and db.storage[k.id].object
|
||
|
if (member and member:alive()) then
|
||
|
f(member)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function set_companion_squad_loot_mode(mode,npc,squad)
|
||
|
local t = {"set_companion_to_loot_items_and_corpses","set_companion_to_loot_nothing"}
|
||
|
local f = this[t[mode]]
|
||
|
if (f) then
|
||
|
squad = squad or get_object_squad(npc)
|
||
|
if (squad and squad.commander_id) then
|
||
|
-- if not (axr_task_manager.hostages_by_id[squad:commander_id()]) then
|
||
|
for k in squad:squad_members() do
|
||
|
local member = db.storage[k.id] and db.storage[k.id].object
|
||
|
if (member and member:alive()) then
|
||
|
f(member)
|
||
|
end
|
||
|
end
|
||
|
-- end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function set_companion_squad_distance_mode(mode,npc,squad)
|
||
|
local t = {"set_companion_to_stay_close","set_companion_to_stay_far"}
|
||
|
local f = this[t[mode]]
|
||
|
if (f) then
|
||
|
squad = squad or get_object_squad(npc)
|
||
|
if (squad and squad.commander_id) then
|
||
|
if not (axr_task_manager.hostages_by_id[squad:commander_id()]) then
|
||
|
for k in squad:squad_members() do
|
||
|
local member = db.storage[k.id] and db.storage[k.id].object
|
||
|
if (member and member:alive()) then
|
||
|
f(member)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- All
|
||
|
function cycle_companions_combat_mode(force_mode, no_msg)
|
||
|
-- local t = {"set_companion_to_attack_state","set_companion_to_ignore_combat_state","set_companion_to_attack_only_actor_combat_enemy_state"}
|
||
|
local t = {"set_companion_to_attack_state","set_companion_to_ignore_combat_state"}
|
||
|
cycle_state( t, "combat", true, force_mode, no_msg )
|
||
|
end
|
||
|
|
||
|
function cycle_companions_move_mode(force_mode, no_msg)
|
||
|
local t = {"set_companion_to_wait_state","set_companion_to_follow_state"}
|
||
|
cycle_state( t, "move", true, force_mode, no_msg )
|
||
|
end
|
||
|
|
||
|
function cycle_companions_stealth_mode(force_mode, no_msg)
|
||
|
local t = {"set_companion_to_stealth_substate","set_companion_to_default_substate"}
|
||
|
cycle_state( t, "stealth", true, force_mode, no_msg )
|
||
|
end
|
||
|
|
||
|
function cycle_companions_loot_mode(force_mode, no_msg)
|
||
|
-- local t = {"set_companion_to_loot_items_and_corpses","set_companion_to_loot_items_only","set_companion_to_loot_corpses_only","set_companion_to_loot_nothing"}
|
||
|
local t = {"set_companion_to_loot_items_and_corpses","set_companion_to_loot_nothing"}
|
||
|
cycle_state( t, "loot", true, force_mode, no_msg )
|
||
|
end
|
||
|
|
||
|
function cycle_companions_distance_mode(force_mode, no_msg)
|
||
|
local t = {"set_companion_to_stay_close","set_companion_to_stay_far"}
|
||
|
cycle_state( t, "distance", false, force_mode, no_msg )
|
||
|
end
|
||
|
|
||
|
function cycle_state(states, key, include_hostages, force_mode, no_msg)
|
||
|
local group = 1
|
||
|
if (hg_companion) then
|
||
|
group = hg_companion:return_group()
|
||
|
end
|
||
|
|
||
|
if (_current_states["squad_"..group][key] + 1 > #states) then
|
||
|
_current_states["squad_"..group][key] = 1
|
||
|
else
|
||
|
_current_states["squad_"..group][key] = _current_states["squad_"..group][key] + 1
|
||
|
end
|
||
|
|
||
|
_current_states["squad_"..group][key] = force_mode or _current_states["squad_"..group][key]
|
||
|
|
||
|
local next_state = states[_current_states["squad_"..group][key]]
|
||
|
|
||
|
if (not no_msg) then
|
||
|
actor_menu.set_msg(1, game.translate_string("st_"..next_state),8)
|
||
|
end
|
||
|
|
||
|
if (group == 0) then
|
||
|
_current_states["squad_1"][key] = _current_states["squad_"..group][key]
|
||
|
_current_states["squad_2"][key] = _current_states["squad_"..group][key]
|
||
|
_current_states["squad_3"][key] = _current_states["squad_"..group][key]
|
||
|
end
|
||
|
|
||
|
local f = this[next_state]
|
||
|
|
||
|
if (f) then
|
||
|
for id,squad in pairs(companion_squads) do
|
||
|
if (squad and squad.commander_id) then
|
||
|
if include_hostages or (not axr_task_manager.hostages_by_id[squad:commander_id()]) then
|
||
|
for k in squad:squad_members() do
|
||
|
if (group == 0 or group == se_load_var(k.id,k.object:name(),"companion_hg")) then
|
||
|
local st = db.storage[k.id]
|
||
|
local npc = st and st.object
|
||
|
if (npc and npc:alive()) then
|
||
|
if (group == 0 or group == se_load_var(npc:id(),npc:name(),"companion_hg")) then
|
||
|
if (npc:has_info("npcx_beh_relax")) then
|
||
|
npc:disable_info_portion("npcx_beh_relax")
|
||
|
xr_sound.stop_sounds_by_id(npc:id())
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
if (key == "move") and (k.id == squad:commander_id()) then
|
||
|
if (st and st.beh) then
|
||
|
st.beh.rally_lvid = nil
|
||
|
end
|
||
|
end
|
||
|
local member = st and st.object
|
||
|
if (member and member:alive()) then
|
||
|
f(member)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- squad update
|
||
|
if (hg_companion) then hg_companion:hg_squad_update() end
|
||
|
end
|
||
|
|
||
|
function move_to_point(p)
|
||
|
local group = 1
|
||
|
if (hg_companion) then
|
||
|
group = hg_companion:return_group()
|
||
|
end
|
||
|
|
||
|
local lvid
|
||
|
if (p==1) then
|
||
|
actor_menu.set_msg(1, game.translate_string("st_hold_then_release"),8)
|
||
|
--_draw_part = particles_object("weapons\\light_signal")
|
||
|
_draw_part = particles_object("_samples_particles_\\flash_light")
|
||
|
--_draw_part = particles_object("static\\net_baspda_blue")
|
||
|
elseif (p==2) then
|
||
|
if (_draw_part) then
|
||
|
local r = level.get_target_dist and level.get_target_dist()
|
||
|
if (r) then
|
||
|
lvid = level.vertex_in_direction(level.vertex_id(device().cam_pos),device().cam_dir,r)
|
||
|
local pos = level.vertex_position(lvid)
|
||
|
--if not(_draw_part:playing()) then
|
||
|
_draw_part:play_at_pos(vector():set(pos.x,pos.y-0.5,pos.z))
|
||
|
--else
|
||
|
-- _draw_part:move_to(pos,pos)
|
||
|
--end
|
||
|
end
|
||
|
end
|
||
|
else
|
||
|
if (_draw_part) then
|
||
|
_draw_part:stop()
|
||
|
_draw_part = nil
|
||
|
end
|
||
|
local r = level.get_target_dist and level.get_target_dist()
|
||
|
if (r) then
|
||
|
lvid = level.vertex_in_direction(level.vertex_id(device().cam_pos),device().cam_dir,r)
|
||
|
if (lvid) then
|
||
|
_current_states["squad_"..group]["move"] = 1
|
||
|
actor_menu.set_msg(1, game.translate_string("st_move_to_point"),8)
|
||
|
for id,squad in pairs(companion_squads) do
|
||
|
if (squad and squad.commander_id) then
|
||
|
for k in squad:squad_members() do
|
||
|
|
||
|
if (group == 0 or group == se_load_var(k.id,k.object:name(),"companion_hg")) then
|
||
|
local st = db.storage[k.id]
|
||
|
local npc = st and st.object
|
||
|
if (npc and npc:alive()) then
|
||
|
if (group == 0 or group == se_load_var(npc:id(),npc:name(),"companion_hg")) then
|
||
|
if (npc:has_info("npcx_beh_relax")) then
|
||
|
npc:disable_info_portion("npcx_beh_relax")
|
||
|
xr_sound.stop_sounds_by_id(npc:id())
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
local member = st and st.object
|
||
|
if (member and member:alive()) then
|
||
|
set_companion_to_follow_state(member)
|
||
|
save_var(member,"fight_from_point",lvid)
|
||
|
end
|
||
|
if (not axr_task_manager.hostages_by_id[k.id]) then
|
||
|
if (st and st.beh) then
|
||
|
st.beh.rally_lvid = lvid
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--------------------------------------------------
|
||
|
-- HG Companion
|
||
|
--------------------------------------------------
|
||
|
|
||
|
function companion_relax()
|
||
|
local group = 0
|
||
|
if (hg_companion) then
|
||
|
group = hg_companion:return_group()
|
||
|
end
|
||
|
|
||
|
if (group == 0) then
|
||
|
_current_states["squad_0"]["move"] = 1
|
||
|
_current_states["squad_1"]["move"] = 1
|
||
|
_current_states["squad_2"]["move"] = 1
|
||
|
_current_states["squad_3"]["move"] = 1
|
||
|
else
|
||
|
_current_states["squad_"..group]["move"] = 1
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--------------------------------------------------
|
||
|
-- Inventory
|
||
|
--------------------------------------------------
|
||
|
|
||
|
function compare_smaller(a,b) return a[2] < b[2] end
|
||
|
function get_inventory_table(npc, npc_id, mode)
|
||
|
npc = npc or get_object_by_id(npc_id)
|
||
|
if (not npc) then
|
||
|
printf("!ERROR axr_companions.get_inventory_table | no npc object recieved")
|
||
|
return {}
|
||
|
end
|
||
|
npc_id = npc_id or npc:id()
|
||
|
|
||
|
local t = {}
|
||
|
|
||
|
-- Collect equipped items and usable ammo
|
||
|
local ignore_ids = {}
|
||
|
local ignore_sections = {}
|
||
|
if (mode == 3) then
|
||
|
for i=1,13 do
|
||
|
if (i ~= 4) then
|
||
|
local itm_slot = npc:item_in_slot(i)
|
||
|
if (itm_slot) then
|
||
|
ignore_ids[itm_slot:id()] = true
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
for i=1,3 do
|
||
|
local wpn = npc:item_in_slot(i)
|
||
|
if (wpn) then
|
||
|
local ammos = utils_item.get_ammo(wpn:section(), wpn:id(), true)
|
||
|
for sec,_ in pairs(ammos) do
|
||
|
ignore_sections[sec] = true
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Iterate through NPC inventory to collect items
|
||
|
local function iterate(owner,item)
|
||
|
local item_id = item:id()
|
||
|
local item_sec = item:section()
|
||
|
|
||
|
-- Mode 1: player's assigned items
|
||
|
if mode == 1 then
|
||
|
if is_assigned_item(npc_id,item_id) then
|
||
|
t[item_id] = item_sec
|
||
|
|
||
|
elseif IsWeapon(item) and se_load_var(item_id, nil, "strapped_item") then -- Include player's strapped weapons
|
||
|
t[item_id] = item_sec
|
||
|
end
|
||
|
|
||
|
-- Mode 2: all excluding technical items
|
||
|
elseif (mode == 2) or (mode == 3) then
|
||
|
if ini_sys:r_bool_ex(item_sec,"can_trade")
|
||
|
and (not ini_sys:r_bool_ex(item_sec,"quest_item"))
|
||
|
and (not IsItem("anim", item_sec))
|
||
|
and (not IsBolt(item))
|
||
|
then
|
||
|
-- Mode 3: exclude items on belt and ammo
|
||
|
if (mode == 3) then
|
||
|
if (not ignore_ids[item_id])
|
||
|
and (not ignore_sections[item_sec])
|
||
|
and (not owner:is_on_belt(item))
|
||
|
then
|
||
|
t[item_id] = item_sec
|
||
|
end
|
||
|
else
|
||
|
t[item_id] = item_sec
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- No mode: all items
|
||
|
else
|
||
|
t[item_id] = item_sec
|
||
|
end
|
||
|
|
||
|
|
||
|
end
|
||
|
npc:iterate_inventory(iterate,npc)
|
||
|
|
||
|
return t
|
||
|
end
|
||
|
|
||
|
function transfer_item(item_id, npc_from, npc_to, no_snd)
|
||
|
local item = level.object_by_id(item_id)
|
||
|
if not (item and npc_from and npc_to) then
|
||
|
printe('ERROR axr_companions.trasnfer_item | no object recieved | item: %s - npc_from: %s - npc_to: %s', (item and true), (npc_from and true), (npc_to and true))
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local id_from = npc_from:id()
|
||
|
local id_to = npc_to:id()
|
||
|
|
||
|
-- from player to companion
|
||
|
if (id_from == AC_ID) then
|
||
|
if not assigned_items[id_to] then
|
||
|
assigned_items[id_to] = {}
|
||
|
end
|
||
|
assigned_items[id_to][item_id] = true
|
||
|
|
||
|
-- from companion to player
|
||
|
else
|
||
|
if assigned_items[id_from] and assigned_items[id_from][item_id] then
|
||
|
assigned_items[id_from][item_id] = nil
|
||
|
if is_empty(assigned_items[id_from]) then
|
||
|
assigned_items[id_from] = nil
|
||
|
end
|
||
|
elseif IsWeapon(item) and se_load_var(item_id, nil, "strapped_item") then -- Include player's strapped weapons
|
||
|
--
|
||
|
else
|
||
|
printf('~ axr_companions.trasnfer_item | item [%s] is not assigned to companion [%s] | assigned companion: %s - assigned item: %s', item:name(), npc_from:name(), is_assigned_companion(npc_from), is_assigned_item(id_from,item_id))
|
||
|
return
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- transfer item
|
||
|
npc_from:transfer_item(item, npc_to)
|
||
|
|
||
|
-- play sound effect
|
||
|
if (not no_snd) then
|
||
|
itms_manager.play_item_sound(item)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function transfer_all_item(npc_from, npc_to)
|
||
|
local id_from = npc_from:id()
|
||
|
local mode = (id_from == AC_ID) and 3 or 1
|
||
|
|
||
|
-- collect inventory items
|
||
|
local inv = get_inventory_table(npc_from, id_from, mode)
|
||
|
|
||
|
if is_empty(inv) then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
-- transfer items
|
||
|
for id,sec in pairs(inv) do
|
||
|
transfer_item(id, npc_from, npc_to, true)
|
||
|
end
|
||
|
|
||
|
-- play sound effect
|
||
|
utils_obj.play_sound("interface\\items\\inv_items_take_all")
|
||
|
end
|
||
|
|
||
|
function is_assigned_item(npc_id, item_id)
|
||
|
if not (npc_id and item_id) then
|
||
|
printf('! is_assigned_item: one or more arguments nil: %s %s',npc_id, item_id)
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
if assigned_items[npc_id] then
|
||
|
return assigned_items[npc_id][item_id]
|
||
|
end
|
||
|
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
function is_overweight(npc, npc_id, comp_weight)
|
||
|
--return utils_item.is_overweight(npc, npc_id)
|
||
|
|
||
|
npc = npc or get_object_by_id(npc_id)
|
||
|
if (not npc) then
|
||
|
return
|
||
|
end
|
||
|
npc_id = npc_id or npc:id()
|
||
|
|
||
|
local max_weight = 30
|
||
|
local curr_weight = 0
|
||
|
if assigned_items[npc_id] then
|
||
|
for id,_ in pairs(assigned_items[npc_id]) do
|
||
|
local obj = level.object_by_id(id)
|
||
|
if obj then
|
||
|
curr_weight = curr_weight + obj:weight()
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if comp_weight then
|
||
|
return (comp_weight > (max_weight - curr_weight))
|
||
|
end
|
||
|
return curr_weight > max_weight, curr_weight, max_weight
|
||
|
end
|
||
|
|
||
|
function get_inventory_weight(tbl_inv)
|
||
|
local inv_weight = 0
|
||
|
for id,sec in pairs(tbl_inv) do
|
||
|
inv_weight = inv_weight + (ini_sys:r_float_ex(sec,"inv_weight") or 0)
|
||
|
end
|
||
|
return inv_weight
|
||
|
end
|
||
|
|
||
|
function OnItemBeforeMove(flags, npc_id, obj, mode, bag_from)
|
||
|
if (mode ~= "loot") or (bag_from ~= EDDListType.iActorBag) then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
--if not (npc_id and non_task_companions[npc_id]) then
|
||
|
if not (npc_id and se_load_var(npc_id, nil, "companion")) then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local overweight, a, b = is_overweight(nil, npc_id) --utils_item.is_overweight(nil, npc_id)
|
||
|
if overweight then
|
||
|
flags.ret_value = false
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function OnItemAfterMove(npc_id, obj, mode, bag_from)
|
||
|
if (mode ~= "loot") then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
--if not (npc_id and non_task_companions[npc_id]) then
|
||
|
if not (npc_id and se_load_var(npc_id, nil, "companion")) then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local item_id = obj:id()
|
||
|
|
||
|
-- Move from actor to NPC
|
||
|
if (bag_from == EDDListType.iActorBag) then
|
||
|
if not assigned_items[npc_id] then
|
||
|
assigned_items[npc_id] = {}
|
||
|
end
|
||
|
assigned_items[npc_id][item_id] = true
|
||
|
|
||
|
-- Move from NPC to actor
|
||
|
elseif (bag_from == EDDListType.iDeadBodyBag) then
|
||
|
if assigned_items[npc_id] and assigned_items[npc_id][item_id] then
|
||
|
assigned_items[npc_id][item_id] = nil
|
||
|
if is_empty(assigned_items[npc_id]) then
|
||
|
assigned_items[npc_id] = nil
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- dialog
|
||
|
function start_trade(a,b)
|
||
|
local npc = a:id() > 0 and a or b
|
||
|
ui_companion_inv.start(npc)
|
||
|
end
|
||
|
|
||
|
function is_assigned_companion(a,b)
|
||
|
local npc = a:id() > 0 and a or b
|
||
|
local npc_id = npc and npc:id()
|
||
|
|
||
|
if not (npc_id) then
|
||
|
printf('! is_assigned_companion: id is nil: %s %s',npc_id)
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
return assigned_items[npc_id] and true or false
|
||
|
end
|
||
|
|
||
|
function give_all_to_companion(a,b)
|
||
|
local npc = (a:id() ~= AC_ID) and a or b
|
||
|
transfer_all_item(db.actor, npc)
|
||
|
end
|
||
|
|
||
|
function take_all_from_companion(a,b)
|
||
|
local npc = (a:id() ~= AC_ID) and a or b
|
||
|
transfer_all_item(npc, db.actor)
|
||
|
end
|
||
|
|
||
|
function can_handle_all_items(a,b)
|
||
|
local npc = (a:id() ~= AC_ID) and a or b
|
||
|
|
||
|
-- actor inventory weight
|
||
|
local inv = get_inventory_table(db.actor, AC_ID, 3)
|
||
|
local inv_weight = get_inventory_weight(inv)
|
||
|
|
||
|
-- npc carry weight
|
||
|
local over_weight, tot_weight, max_weight = is_overweight(npc)
|
||
|
return (inv_weight <= (max_weight - tot_weight))
|
||
|
end
|
||
|
|
||
|
function can_not_handle_all_items(a,b)
|
||
|
return (not can_handle_all_items(a,b))
|
||
|
end
|
||
|
|
||
|
--------------------------------------------------
|
||
|
-- keybinding
|
||
|
--------------------------------------------------
|
||
|
|
||
|
axr_keybind.bind("kCUSTOM3",function(p)
|
||
|
move_to_point(p)
|
||
|
end)
|
||
|
|
||
|
function on_key_release(key)
|
||
|
local bind = dik_to_bind(key)
|
||
|
if (bind == key_bindings.kCUSTOM18) then
|
||
|
start_CW()
|
||
|
|
||
|
elseif (bind == key_bindings.kCUSTOM1) then
|
||
|
cycle_companions_combat_mode()
|
||
|
|
||
|
elseif (bind == key_bindings.kCUSTOM2) then
|
||
|
cycle_companions_move_mode()
|
||
|
|
||
|
elseif (bind == key_bindings.kCUSTOM3) then
|
||
|
--move_to_point(p)
|
||
|
|
||
|
elseif (bind == key_bindings.kCUSTOM4) then
|
||
|
cycle_companions_stealth_mode()
|
||
|
|
||
|
elseif (bind == key_bindings.kCUSTOM5) then
|
||
|
cycle_companions_loot_mode()
|
||
|
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--------------------------------------------------
|
||
|
-- Companions Wheel
|
||
|
--------------------------------------------------
|
||
|
|
||
|
local max_trade_distance = 8 --[m]
|
||
|
local cw_cooldown = 0 --[ms]
|
||
|
local cw_commands = {}
|
||
|
|
||
|
function cw_prepare()
|
||
|
cw_commands["combat"] = { key= "DIK_1" ,on_state= 1 ,get= get_companion_combat_mode ,set_one= set_companion_squad_combat_mode ,set_all= cycle_companions_combat_mode ,on_str= "st_set_companion_to_attack_state" ,off_str= "st_set_companion_to_ignore_combat_state" }
|
||
|
cw_commands["movement"] = { key= "DIK_2" ,on_state= 2 ,get= get_companion_move_mode ,set_one= set_companion_squad_move_mode ,set_all= cycle_companions_move_mode ,on_str= "st_set_companion_to_follow_state" ,off_str= "st_set_companion_to_wait_state" }
|
||
|
cw_commands["stealth"] = { key= "DIK_3" ,on_state= 1 ,get= get_companion_stealth_mode ,set_one= set_companion_squad_stealth_mode ,set_all= cycle_companions_stealth_mode ,on_str= "st_set_companion_to_stealth_substate" ,off_str= "st_set_companion_to_default_substate" }
|
||
|
cw_commands["distance"] = { key= "DIK_4" ,on_state= 2 ,get= get_companion_distance_mode ,set_one= set_companion_squad_distance_mode ,set_all= cycle_companions_distance_mode ,on_str= "st_set_companion_to_stay_far" ,off_str= "st_set_companion_to_stay_close" }
|
||
|
cw_commands["loot"] = { key= "DIK_5" ,on_state= 1 ,get= get_companion_loot_mode ,set_one= set_companion_squad_loot_mode ,set_all= cycle_companions_loot_mode ,on_str= "st_set_companion_to_loot_items_and_corpses" ,off_str= "st_set_companion_to_loot_nothing" }
|
||
|
cw_commands["trade"] = { key= "DIK_6" ,on_state= 1 ,get= companion_inventory_mode ,set_one= start_trade ,off_str= "st_set_companion_to_trade" }
|
||
|
end
|
||
|
|
||
|
function get_nearby_companion()
|
||
|
local obj = level.get_target_obj()
|
||
|
if obj and obj:has_info("npcx_is_companion") and (distance_between(db.actor,obj) <= max_trade_distance) then
|
||
|
return obj
|
||
|
|
||
|
elseif is_not_empty(companion_squads) then
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
function companion_inventory_mode(npc)
|
||
|
return npc and 1 or 2
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------
|
||
|
GUI = nil -- instance, don't touch
|
||
|
function start_CW()
|
||
|
local obj = get_nearby_companion()
|
||
|
if (not obj) then
|
||
|
actor_menu.set_msg(1, game.translate_string("st_no_companions"),8)
|
||
|
return
|
||
|
end
|
||
|
|
||
|
hide_hud_inventory()
|
||
|
|
||
|
if (not GUI) then
|
||
|
GUI = UIWheelCompanion()
|
||
|
end
|
||
|
|
||
|
if (GUI) and (not GUI:IsShown()) then
|
||
|
GUI:ShowDialog(true)
|
||
|
|
||
|
if obj == true then
|
||
|
GUI:Reset()
|
||
|
else
|
||
|
GUI:Reset(obj)
|
||
|
end
|
||
|
|
||
|
cw_cooldown = time_global()
|
||
|
|
||
|
Register_UI("UIWheelCompanion","ui_wheel_companion")
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class "UIWheelCompanion" (CUIScriptWnd)
|
||
|
|
||
|
function UIWheelCompanion:__init() super()
|
||
|
self.clr_on = GetARGB(255, 255, 255, 255)
|
||
|
self.clr_off = GetARGB(70, 255, 255, 255)
|
||
|
self.states = {}
|
||
|
|
||
|
if is_empty(cw_commands) then
|
||
|
cw_prepare()
|
||
|
end
|
||
|
|
||
|
self:InitControls()
|
||
|
self:InitCallBacks()
|
||
|
end
|
||
|
|
||
|
function UIWheelCompanion:__finalize()
|
||
|
end
|
||
|
|
||
|
function UIWheelCompanion:InitControls()
|
||
|
self:SetWndRect (Frect():set(0,0,1024,768))
|
||
|
self:SetAutoDelete(true)
|
||
|
self:AllowMovement(true)
|
||
|
|
||
|
self.xml = CScriptXmlInit()
|
||
|
local xml = self.xml
|
||
|
xml:ParseFile ("ui_wheel_companion.xml")
|
||
|
|
||
|
-- Main
|
||
|
self.dialog = xml:InitStatic("wheel", self)
|
||
|
xml:InitStatic("wheel:background", self.dialog)
|
||
|
|
||
|
-- Buttons
|
||
|
self.cmd_pic = {}
|
||
|
self.cmd_btn = {}
|
||
|
for cmd,v in pairs(cw_commands) do
|
||
|
self.cmd_pic[cmd] = xml:InitStatic("wheel:box_" .. cmd, self.dialog)
|
||
|
self.cmd_pic[cmd]:InitTexture( "ui_companion_" .. cmd )
|
||
|
self.cmd_pic[cmd]:SetStretchTexture(true)
|
||
|
--self.cmd_pic[cmd]:SetTextureColor( self.clr_off )
|
||
|
|
||
|
self.cmd_btn[cmd] = xml:Init3tButton("wheel:btn_" .. cmd, self.dialog)
|
||
|
self:Register(self.cmd_btn[cmd],"btn_" .. cmd)
|
||
|
end
|
||
|
|
||
|
-- Text
|
||
|
self.hint = xml:InitTextWnd("wheel:hint", self.dialog)
|
||
|
|
||
|
-- Extended
|
||
|
self.dialog_info = xml:InitStatic("info", self)
|
||
|
xml:InitStatic("info:background", self.dialog_info)
|
||
|
|
||
|
-- Icon and name
|
||
|
self.icon = xml:InitStatic("info:icon", self.dialog_info)
|
||
|
self.name = xml:InitTextWnd("info:name", self.dialog_info)
|
||
|
|
||
|
-- Health
|
||
|
self.dialog_health = xml:InitStatic("info:health", self.dialog_info)
|
||
|
xml:InitStatic("info:health:caption", self.dialog_health)
|
||
|
xml:InitStatic("info:health:prog_back", self.dialog_health)
|
||
|
self.prog_health = xml:InitProgressBar("info:health:value", self.dialog_health)
|
||
|
self.prog_health:Show(true)
|
||
|
self.prog_health:SetProgressPos(0.0)
|
||
|
|
||
|
-- Carry weight
|
||
|
self.dialog_weight = xml:InitStatic("info:weight", self.dialog_info)
|
||
|
xml:InitStatic("info:weight:caption", self.dialog_weight)
|
||
|
xml:InitStatic("info:weight:prog_back", self.dialog_weight)
|
||
|
self.prog_weight = xml:InitProgressBar("info:weight:value", self.dialog_weight)
|
||
|
self.prog_weight:Show(true)
|
||
|
self.prog_weight:SetProgressPos(0.0)
|
||
|
end
|
||
|
|
||
|
function UIWheelCompanion:InitCallBacks()
|
||
|
for cmd,v in pairs(cw_commands) do
|
||
|
local _wrapper = function(handler) -- we need wrapper in order to pass ctrl to method
|
||
|
self:Order(cmd)
|
||
|
end
|
||
|
self:AddCallback("btn_" .. cmd, ui_events.BUTTON_CLICKED, _wrapper, self)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
function UIWheelCompanion:Reset(npc)
|
||
|
self.id = npc and npc:id() or false
|
||
|
|
||
|
for cmd,v in pairs(cw_commands) do
|
||
|
local state = v.get(npc)
|
||
|
self.cmd_pic[cmd]:SetTextureColor( state == v.on_state and self.clr_on or self.clr_off )
|
||
|
|
||
|
self.states[cmd] = (state == v.on_state)
|
||
|
end
|
||
|
|
||
|
self.dialog_info:Show(npc and true or false)
|
||
|
if (npc) then
|
||
|
local overweight, w_total, w_max = is_overweight(npc) --utils_item.is_overweight(npc)
|
||
|
|
||
|
self.icon:InitTexture( npc:character_icon() )
|
||
|
self.name:SetText( npc:character_name() )
|
||
|
self.prog_health:SetProgressPos( npc.health )
|
||
|
self.prog_weight:SetProgressPos( w_total / w_max )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function UIWheelCompanion:Update()
|
||
|
CUIScriptWnd.Update(self)
|
||
|
|
||
|
for cmd,element in pairs(self.cmd_btn) do
|
||
|
if element:IsCursorOverWindow() then
|
||
|
local state = self.states[cmd]
|
||
|
local str = state and cw_commands[cmd].off_str or cw_commands[cmd].on_str
|
||
|
self.hint:SetText(str and game.translate_string(str) or "")
|
||
|
return
|
||
|
end
|
||
|
end
|
||
|
|
||
|
self.hint:SetText("")
|
||
|
end
|
||
|
|
||
|
function UIWheelCompanion:Order(cmd)
|
||
|
--printf(cmd)
|
||
|
local obj
|
||
|
if self.id then
|
||
|
obj = db.storage[self.id] and db.storage[self.id].object or level.object_by_id(self.id)
|
||
|
end
|
||
|
local state = cw_commands[cmd].get(obj)
|
||
|
local next_state = (state == 2) and 1 or 2
|
||
|
if obj then
|
||
|
if cw_commands[cmd].set_one then
|
||
|
if cmd == "trade" then
|
||
|
cw_commands[cmd].set_one(obj)
|
||
|
else
|
||
|
cw_commands[cmd].set_one(next_state,obj)
|
||
|
end
|
||
|
end
|
||
|
elseif cw_commands[cmd].set_all then
|
||
|
cw_commands[cmd].set_all()
|
||
|
end
|
||
|
self:Close()
|
||
|
end
|
||
|
|
||
|
|
||
|
function UIWheelCompanion:OnKeyboard(dik, keyboard_action)
|
||
|
local res = CUIScriptWnd.OnKeyboard(self,dik,keyboard_action)
|
||
|
if (res == false) then
|
||
|
if keyboard_action == ui_events.WINDOW_KEY_RELEASED then
|
||
|
|
||
|
local bind = dik_to_bind(dik)
|
||
|
if (time_global() > cw_cooldown + 100) and (bind == key_bindings.kQUIT or bind == key_bindings.kUSE or bind == key_bindings.kCUSTOM18) then
|
||
|
self:Close()
|
||
|
return
|
||
|
end
|
||
|
|
||
|
for cmd,v in pairs(cw_commands) do
|
||
|
if v.key and (dik == DIK_keys[v.key]) then
|
||
|
self:Order(cmd)
|
||
|
pass = false
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
return res
|
||
|
end
|
||
|
|
||
|
function UIWheelCompanion:Close()
|
||
|
if self:IsShown() then
|
||
|
self:HideDialog()
|
||
|
self:Show(false)
|
||
|
|
||
|
Unregister_UI("UIWheelCompanion")
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--------------------------------------------------
|
||
|
-- Companions List
|
||
|
--------------------------------------------------
|
||
|
|
||
|
HUD = nil
|
||
|
function activate_hud()
|
||
|
RegisterScriptCallback("actor_on_net_destroy",actor_on_net_destroy)
|
||
|
RegisterScriptCallback("on_console_execute",on_console_execute)
|
||
|
RegisterScriptCallback("GUI_on_show",update_hud)
|
||
|
RegisterScriptCallback("GUI_on_hide",update_hud)
|
||
|
|
||
|
if HUD == nil then
|
||
|
HUD = UICompanionList()
|
||
|
get_hud():AddDialogToRender(HUD)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function deactivate_hud()
|
||
|
if HUD ~= nil then
|
||
|
get_hud():RemoveDialogToRender(HUD)
|
||
|
HUD = nil
|
||
|
end
|
||
|
|
||
|
UnregisterScriptCallback("actor_on_net_destroy",actor_on_net_destroy)
|
||
|
UnregisterScriptCallback("on_console_execute",on_console_execute)
|
||
|
UnregisterScriptCallback("GUI_on_show",update_hud)
|
||
|
UnregisterScriptCallback("GUI_on_hide",update_hud)
|
||
|
end
|
||
|
|
||
|
function update_hud()
|
||
|
if HUD ~= nil then
|
||
|
HUD:Update(true)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function actor_on_net_destroy()
|
||
|
if HUD ~= nil then
|
||
|
get_hud():RemoveDialogToRender(HUD)
|
||
|
HUD = nil
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function on_console_execute(name)
|
||
|
if name == "hud_draw" and HUD then
|
||
|
HUD:Update(true)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
---------------------------------------
|
||
|
class "UICompanionList" (CUIScriptWnd)
|
||
|
|
||
|
function UICompanionList:__init() super()
|
||
|
self.companion_info = {}
|
||
|
self._tmr = time_global()
|
||
|
self.update_rate = 1000 --[ms]
|
||
|
self.clr_list = {
|
||
|
["def"] = GetARGB(255,255,255,255),
|
||
|
["stalker"] = GetARGB(255,255,255,100),
|
||
|
["bandit"] = GetARGB(255,120,201,79),
|
||
|
["ecolog"] = GetARGB(255,255,128,100),
|
||
|
["csky"] = GetARGB(255,100,200,255),
|
||
|
["dolg"] = GetARGB(255,255,100,100),
|
||
|
["freedom"] = GetARGB(255,100,255,100),
|
||
|
["killer"] = GetARGB(255,100,100,255),
|
||
|
["army"] = GetARGB(255,100,128,255),
|
||
|
["monolith"]= GetARGB(255,120,201,79),
|
||
|
}
|
||
|
|
||
|
self:InitControls()
|
||
|
end
|
||
|
|
||
|
function UICompanionList:__finalize()
|
||
|
end
|
||
|
|
||
|
function UICompanionList:InitControls()
|
||
|
local xml = utils_xml.get_hud_xml()
|
||
|
|
||
|
self.dialog = xml:InitStatic("companion_list", self)
|
||
|
--utils_xml.correct_ratio(self.dialog, true)
|
||
|
|
||
|
self.companion_info = {}
|
||
|
for i=1,8 do
|
||
|
self.companion_info[i] = {}
|
||
|
self.companion_info[i].base = xml:InitStatic("companion_list:slot", self.dialog)
|
||
|
self.companion_info[i].background = xml:InitStatic("companion_list:slot:background", self.companion_info[i].base)
|
||
|
self.companion_info[i].icon = xml:InitStatic("companion_list:slot:icon", self.companion_info[i].base)
|
||
|
self.companion_info[i].danger_indicator = xml:InitStatic("companion_list:slot:danger_indicator", self.companion_info[i].base)
|
||
|
self.companion_info[i].team_role_shadow = xml:InitStatic("companion_list:slot:team_role", self.companion_info[i].base)
|
||
|
self.companion_info[i].team_role = xml:InitStatic("companion_list:slot:team_role", self.companion_info[i].base)
|
||
|
self.companion_info[i].distance = xml:InitTextWnd("companion_list:slot:distance", self.companion_info[i].base)
|
||
|
self.companion_info[i].prog_health = xml:InitProgressBar("companion_list:slot:health", self.companion_info[i].base)
|
||
|
|
||
|
for _,ele in pairs(self.companion_info[i]) do
|
||
|
utils_xml.correct_ratio(ele)
|
||
|
end
|
||
|
|
||
|
local h = self.companion_info[i].background:GetHeight()
|
||
|
self.companion_info[i].distance:SetFont(GetFontSmall())
|
||
|
self.companion_info[i].base:SetWndPos( vector2():set( 0 , (8-i)*(h+10) ) )
|
||
|
|
||
|
local pos = self.companion_info[i].team_role:GetWndPos()
|
||
|
self.companion_info[i].team_role_shadow:SetWndPos( vector2():set( pos.x + 1 , pos.y + 2 ) )
|
||
|
self.companion_info[i].team_role_shadow:SetTextureColor( GetARGB(255, 0, 0, 0) )
|
||
|
end
|
||
|
|
||
|
-- HG
|
||
|
local hud = false
|
||
|
if (hg_companion) then
|
||
|
hud = hg_companion:return_hud()
|
||
|
end
|
||
|
|
||
|
if (self.companion_info[1] and hg_companion and hud == true) then
|
||
|
xml = hg_companion:get_hg_xml()
|
||
|
self.dialog = xml:InitStatic("hg_ui_companion", self)
|
||
|
|
||
|
self.companion_info[0] = {}
|
||
|
self.companion_info[0].base = xml:InitStatic("hg_ui_companion:slot", self.dialog)
|
||
|
self.companion_info[0].background = xml:InitStatic("hg_ui_companion:slot:background", self.companion_info[0].base)
|
||
|
self.companion_info[0].move = xml:InitStatic("hg_ui_companion:slot:move", self.companion_info[0].base)
|
||
|
self.companion_info[0].group = xml:InitStatic("hg_ui_companion:slot:group", self.companion_info[0].base)
|
||
|
|
||
|
for _,hg in pairs(self.companion_info[0]) do
|
||
|
utils_xml.correct_ratio(hg)
|
||
|
end
|
||
|
|
||
|
local h = self.companion_info[1].background:GetHeight()
|
||
|
self.companion_info[0].base:SetWndPos( vector2():set( 0 , (8)*(h+10) ) )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function UICompanionList:Update(force)
|
||
|
CUIScriptWnd.Update(self)
|
||
|
|
||
|
local tg = time_global()
|
||
|
if force then
|
||
|
self._tmr = tg - 1
|
||
|
end
|
||
|
if self._tmr >= tg then
|
||
|
return
|
||
|
end
|
||
|
self._tmr = tg + self.update_rate
|
||
|
|
||
|
local to_show = main_hud_shown()
|
||
|
local clist = list_actor_squad_by_id()
|
||
|
local count = 0
|
||
|
|
||
|
for i=1,8 do
|
||
|
local se_obj = clist[i] and alife_object(clist[i])
|
||
|
if to_show and (se_obj and IsStalker(nil,se_obj:clsid()) and se_obj:alive()) then
|
||
|
local st = db.storage[se_obj.id]
|
||
|
local npc = st and st.object
|
||
|
local ele = self.companion_info[i]
|
||
|
|
||
|
-- Icon
|
||
|
local icon_name = npc and npc:character_icon() or se_obj:character_icon()
|
||
|
icon_name = icon_name and icon_name ~= "" and icon_name or "ui\\ui_noise"
|
||
|
ele.icon:InitTexture(icon_name)
|
||
|
|
||
|
-- Distance
|
||
|
ele.distance:SetText(string.format("%.2f M", se_obj.position:distance_to(db.actor:position())))
|
||
|
|
||
|
-- Leader and relation status
|
||
|
local squad = se_obj.group_id and se_obj.group_id ~= 65535 and alife_object(se_obj.group_id)
|
||
|
if (squad and squad:commander_id() == se_obj.id) then
|
||
|
ele.team_role:InitTexture("ui_minimap_squad_leader")
|
||
|
ele.team_role_shadow:InitTexture("ui_minimap_squad_leader")
|
||
|
else
|
||
|
ele.team_role:InitTexture("ui_minimap_point")
|
||
|
ele.team_role_shadow:InitTexture("ui_minimap_point")
|
||
|
end
|
||
|
|
||
|
local community = npc and npc:character_community()
|
||
|
local clr = community and self.clr_list[community] or self.clr_list["def"]
|
||
|
ele.team_role:SetTextureColor(clr)
|
||
|
|
||
|
-- Health
|
||
|
if (npc) then
|
||
|
local health = clamp( round_idp(npc.health,1),0,1)
|
||
|
ele.prog_health:SetProgressPos( health )
|
||
|
end
|
||
|
|
||
|
-- Danger state
|
||
|
ele.danger_indicator:Show(npc and npc:best_enemy() and true or false)
|
||
|
|
||
|
self.companion_info[i].base:Show(true)
|
||
|
count = count + 1
|
||
|
else
|
||
|
self.companion_info[i].base:Show(false)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- HG
|
||
|
local hud = false
|
||
|
if (hg_companion) then
|
||
|
hud = hg_companion:return_hud()
|
||
|
end
|
||
|
|
||
|
local hg = self.companion_info[0]
|
||
|
if (hg ~=nil) then
|
||
|
if (to_show and count > 0 and hud == true) then
|
||
|
local move = 0
|
||
|
local group = 0
|
||
|
if (hg_companion) then
|
||
|
move = hg_companion:return_movement()
|
||
|
group = hg_companion:return_group()
|
||
|
end
|
||
|
hg.move:InitTexture("hg_ui_move_"..move)
|
||
|
hg.group:InitTexture("hg_ui_group_"..group)
|
||
|
hg.base:Show(true)
|
||
|
else
|
||
|
hg.base:Show(false)
|
||
|
end
|
||
|
end
|
||
|
end
|