271 lines
11 KiB
Plaintext
271 lines
11 KiB
Plaintext
weapon_displays = {}
|
|
weapon_display_slots = {}
|
|
|
|
function on_game_start()
|
|
RegisterScriptCallback("ActorMenu_on_item_after_move", ActorMenu_on_item_after_move)
|
|
RegisterScriptCallback("ActorMenu_on_item_before_move", ActorMenu_on_item_before_move)
|
|
RegisterScriptCallback("actor_on_item_before_pickup", actor_on_item_before_pickup)
|
|
RegisterScriptCallback("game_object_on_net_spawn", game_object_on_net_spawn)
|
|
RegisterScriptCallback("save_state", save_state)
|
|
RegisterScriptCallback("load_state", load_state)
|
|
|
|
-- Patch the displayed items into world items, so that npc's don't pick them up
|
|
local base_is_world_item = game_setup.is_world_item
|
|
function game_setup.is_world_item(id)
|
|
for case_id, case_guns in pairs(weapon_displays) do
|
|
for item_id, world_id in pairs(case_guns) do
|
|
if id == world_id then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
return base_is_world_item(id)
|
|
end
|
|
end
|
|
|
|
|
|
valid_actor_bags = {
|
|
[EDDListType.iActorBag] = true,
|
|
[EDDListType.iActorSlot] = true
|
|
}
|
|
|
|
valid_stash = {}
|
|
|
|
valid_display_kind = {}
|
|
|
|
-- probably a bad idea to mix display metadata with pos/rot offsets in seperate sections, but in the same LTX
|
|
local ini_displays = ini_file("items\\settings\\hideout_furniture\\displays\\displays.ltx")
|
|
ini_displays:section_for_each(function(section)
|
|
local slots = ini_displays:r_float_ex(section, "slots")
|
|
if not slots then return end -- exit early if there is no number of slots
|
|
valid_stash[section] = slots
|
|
|
|
local valid_classes = ini_displays:r_list(section, "valid_classes")
|
|
valid_display_kind[section] = {}
|
|
for i, class in pairs(valid_classes) do
|
|
valid_display_kind[section][class] = true
|
|
end
|
|
end)
|
|
|
|
---@param se_obj game_object
|
|
function game_object_on_net_spawn(se_obj)
|
|
for case_id, case_guns in pairs(weapon_displays) do
|
|
for item_id, world_id in pairs(case_guns) do
|
|
if se_obj:id() == world_id then
|
|
CreateTimeEvent("collector","freeze_" .. se_obj:id(), 0, freeze_item, se_obj:id(), item_id)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function ActorMenu_on_item_before_move(flags, case_id, item, mode, bag_from)
|
|
if not (case_id and item and mode == "loot") then return end
|
|
local case_obj = get_object_by_id(case_id)
|
|
local case_sec = case_obj:section()
|
|
if case_obj and case_sec and valid_stash[case_sec] and valid_actor_bags[bag_from] then
|
|
local sec = item:section()
|
|
local kind = SYS_GetParam(0, sec, "kind")
|
|
local case_slot_count = valid_stash[case_sec]
|
|
if weapon_displays[case_id] and size_table(weapon_displays[case_id]) >= case_slot_count or not(kind and valid_display_kind[case_sec][kind]) then
|
|
flags.ret_value = false
|
|
end
|
|
end
|
|
end
|
|
|
|
function ActorMenu_on_item_after_move(case_id, item, mode, bag_from)
|
|
if not (case_id and item and mode == "loot") then return end
|
|
local case_obj = get_object_by_id(case_id)
|
|
local case_sec = case_obj:section()
|
|
if case_obj and case_sec and valid_stash[case_sec] then
|
|
local sec = item:section()
|
|
local kind = SYS_GetParam(0, sec, "kind")
|
|
local item_id = item:id()
|
|
if valid_actor_bags[bag_from] and kind and valid_display_kind[case_sec][kind] then
|
|
|
|
if weapon_displays[case_id] and weapon_displays[case_id][item_id] then
|
|
RemoveTimeEvent("collector","remove_" .. weapon_displays[case_id][item_id])
|
|
else
|
|
if not weapon_displays[case_id] then
|
|
weapon_displays[case_id] = {}
|
|
weapon_display_slots[case_id] = {}
|
|
end
|
|
|
|
local ini_wd = ini_file("items\\settings\\hideout_furniture\\displays\\displays.ltx")
|
|
|
|
local se_case = alife_object(case_id)
|
|
local pos = vector():set(case_obj:position())
|
|
local case_slot_count = valid_stash[case_sec]
|
|
local gun_number = get_first_open_slot(weapon_display_slots[case_id], case_slot_count)
|
|
local gun_section = ini_sys:r_string_ex(sec,"parent_section") or sec
|
|
|
|
-- Rotation --
|
|
-- Pull slot angle for stash
|
|
local angle = vector():set(se_case.angle)
|
|
local angle_q = aol_rotation.Quaternion(angle)
|
|
local angle_str = ini_wd:r_string_ex(case_sec .. "_angles",gun_number)
|
|
|
|
-- Pull angle for gun slot
|
|
local case_slot_angle = vector():set(string_to_vector(angle_str)) -- get specific angle for gun slot
|
|
case_slot_angle = case_slot_angle:mul(0.017453292519943295769236907684886) -- convert to radian
|
|
|
|
-- Pull angle for gun
|
|
local gun_adjust_str = ini_wd:r_string_ex("gun_angle_offsets",gun_section) or "0,0,0"
|
|
local gun_adjust_angle = string_to_vector(gun_adjust_str)
|
|
gun_adjust_angle = gun_adjust_angle:mul(0.017453292519943295769236907684886)
|
|
|
|
local angle_offset = case_slot_angle:add(gun_adjust_angle)
|
|
local angle_offset_q = aol_rotation.Quaternion(angle_offset)
|
|
|
|
local q_rotation = angle_offset_q:multiply(angle_q)
|
|
|
|
-- Location --
|
|
local pos_offset = vector():set(0,0,0)
|
|
|
|
-- Add position offset (weapon-specific, in vanilla)
|
|
local position_offset_str = ini_sys:r_string_ex(sec, "position")
|
|
if position_offset_str then
|
|
local vec = string_to_vector(position_offset_str)
|
|
vec.x = 0 -- ignore x axis offset
|
|
pos_offset:add(vec)
|
|
end
|
|
|
|
-- Pull position adjustment for gun (weapon-display-specific)
|
|
local ini_gun_offsets = ini_file("items\\settings\\hideout_furniture\\item_offsets\\items.ltx")
|
|
local pos_gun_adjust = ini_gun_offsets:r_string_ex("gun_position_offsets",gun_section)
|
|
if pos_gun_adjust then
|
|
-- Rotate offset to align with orientation of stash
|
|
pos_offset:add(string_to_vector(pos_gun_adjust))
|
|
end
|
|
|
|
-- Rotate position offset by unique gun rotation offset
|
|
pos_offset = angle_offset_q:rotate_vector(pos_offset)
|
|
|
|
-- Pull slot positions for stash
|
|
local slot_pos_str = ini_wd:r_string_ex(case_sec .. "_positions",gun_number)
|
|
local slot_pos = vector():set(string_to_vector(slot_pos_str))
|
|
|
|
-- pos_offset = aol_rotation.rotate_vector_by_euler(pos_offset, se_case.angle)
|
|
pos_offset:add(slot_pos)
|
|
pos_offset = angle_q:rotate_vector(pos_offset)
|
|
|
|
pos = pos:add(pos_offset)
|
|
|
|
-- Create object at pos
|
|
local world_se_obj = alife_create_item(sec, { pos, db.actor:level_vertex_id(), db.actor:game_vertex_id() })
|
|
|
|
-- Rotate object
|
|
world_se_obj.angle = q_rotation:to_euler_angles()
|
|
-- world_se_obj.angle = angle
|
|
|
|
-- Set invisible to AI (prevent pickup)
|
|
local data = utils_stpk.get_weapon_data(world_se_obj)
|
|
local remove_flags = 128
|
|
local flag_mask = bit_not(remove_flags)
|
|
data.object_flags = bit_and(data.object_flags, flag_mask)
|
|
|
|
utils_stpk.set_weapon_data(data, world_se_obj)
|
|
release_item_manager.unmark_item(world_se_obj.id)
|
|
|
|
weapon_displays[case_id][item_id] = world_se_obj.id
|
|
weapon_display_slots[case_id][gun_number] = world_se_obj.id
|
|
end
|
|
elseif weapon_displays[case_id] and weapon_displays[case_id][item_id] then
|
|
CreateTimeEvent("collector","remove_" .. weapon_displays[case_id][item_id], 0, remove_displayed_item, item_id, weapon_displays[case_id][item_id], case_id)
|
|
end
|
|
end
|
|
end
|
|
|
|
function string_to_vector(string)
|
|
local t = str_explode(string,",")
|
|
for k, str in pairs(t) do
|
|
t[k] = string.gsub(str, "%s+", "") -- remove whitespaces
|
|
t[k] = tonumber(t[k])
|
|
--printf(str)
|
|
end
|
|
return vector():set(t[1], t[2], t[3])
|
|
end
|
|
|
|
function get_first_open_slot(t, max_slots)
|
|
for i=1, max_slots do
|
|
if not t[i] then
|
|
return i
|
|
end
|
|
end
|
|
end
|
|
|
|
function actor_on_item_before_pickup(obj, flags)
|
|
for case_id, case_guns in pairs(weapon_displays) do
|
|
for item_id, world_id in pairs(case_guns) do
|
|
if obj:id() == world_id then
|
|
--printf("NO PICKUP")
|
|
flags.ret_value = false
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function freeze_item(id, item_id)
|
|
local obj = level.object_by_id(id)
|
|
local phys = obj and obj:get_physics_shell()
|
|
if phys then
|
|
-- Show silencer/launcher if stashed item has the attachment
|
|
if utils_item.has_attached_silencer(get_object_by_id(item_id)) then
|
|
obj:set_bone_visible("wpn_silencer", true, true)
|
|
end
|
|
if utils_item.has_attached_gl(get_object_by_id(item_id)) then
|
|
obj:set_bone_visible("wpn_launcher", true, true)
|
|
end
|
|
|
|
phys:freeze()
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
function remove_displayed_item(item_id, world_item_id, case_id)
|
|
local obj = alife_object(world_item_id)
|
|
if obj then
|
|
alife_release(obj) -- USE safe_release_manager.release(se_obj) ??????
|
|
weapon_displays[case_id][item_id] = nil
|
|
|
|
for slot, id in pairs(weapon_display_slots[case_id]) do
|
|
if id == world_item_id then
|
|
weapon_display_slots[case_id][slot] = nil
|
|
break
|
|
end
|
|
end
|
|
|
|
if size_table(weapon_displays[case_id]) == 0 then
|
|
weapon_displays[case_id] = nil
|
|
weapon_display_slots[case_id] = nil
|
|
end
|
|
|
|
RemoveTimeEvent("collector","freeze_" .. world_item_id)
|
|
--printf("DISPLAYED ITEM REMOVED")
|
|
return true
|
|
end
|
|
--printf("TRYING TO REMOVE")
|
|
return false
|
|
end
|
|
|
|
function vector_rotate_y_radian(v, angle)
|
|
--angle = angle * 0.017453292519943295769236907684886
|
|
local c = math.cos (angle)
|
|
local s = math.sin (angle)
|
|
return vector():set(v.x * c - v.z * s, v.y, v.x * s + v.z * c)
|
|
end
|
|
|
|
function angle_to_radian(angle)
|
|
return angle * 0.017453292519943295769236907684886
|
|
end
|
|
|
|
function save_state(m_data)
|
|
m_data.weapon_displays = weapon_displays
|
|
m_data.weapon_display_slots = weapon_display_slots
|
|
end
|
|
|
|
function load_state(m_data)
|
|
weapon_displays = m_data.weapon_displays or {}
|
|
weapon_display_slots = m_data.weapon_display_slots or {}
|
|
end |