Divergent/mods/Weapon Cover Tilt/gamedata/scripts/weapon_cover_tilt.script

1055 lines
33 KiB
Plaintext
Raw Permalink Normal View History

2024-03-17 20:18:03 -04:00
-- Weapon cover tilt script
-- Makes weapon go up when near obstacle to emulate weapon dimensions
-- Written by demonized
-- Current version
VERSION = 14
--Protected function call to prevent crashes to desktop
--Prints error in console if occured, otherwise proceed normally
--Use for test only, slower than usual
local try = try or function(func, ...)
local status, error_or_result = pcall(func, ...)
if not status then
printf(error_or_result)
return false, status, error_or_result
else
return error_or_result, status
end
end
local dte = demonized_time_events
local normalize = normalize
local clamp = clamp
local abs = math.abs
local min = math.min
local max = math.max
local sqrt = math.sqrt
local actor_weapon_lowered = game.actor_weapon_lowered
local get_target_dist = level.get_target_dist
local get_target_obj = level.get_target_obj
--EMA smoothing for changing values, frame independent
local default_smoothing = 11.5
local smoothed_values = {}
local function ema(key, value, def, steps, delta)
local steps = steps or default_smoothing
local delta = delta or steps
local smoothing_alpha = 2.0 / (steps + 1)
smoothed_values[key] = smoothed_values[key] and smoothed_values[key] + min(smoothing_alpha * (delta / steps), 1) * (value - smoothed_values[key]) or def or value
--printf("EMA fired, key %s, target %s, current %s, going %s", key, value, smoothed_values[key], (value > smoothed_values[key] and "up" or "down"))
return smoothed_values[key]
end
-- Linear inter/extrapolation
local function lerp(a, b, f)
if a and b and f then
return a + f * (b - a)
else
return a or b or 0
end
end
local intercepts = {
["actor_on_weapon_before_tilt"] = {}, -- Params: weapon_object, flags
["actor_on_weapon_tilt_start"] = {}, -- Params: weapon_object
["actor_on_weapon_tilt_end"] = {}, -- Params: weapon_object
["actor_on_weapon_tilting"] = {}, -- Params: weapon_object, coeff
["actor_on_weapon_tilting_back"] = {}, -- Params: weapon_object, coeff
}
function add_intercept(name)
if intercepts[name] then return end
intercepts[name] = {}
end
function add_callback(name, func)
if not (name and func and intercepts[name]) then return end
intercepts[name][func] = true
end
function remove_callback(name, func)
if not (name and func and intercepts[name]) then return end
intercepts[name][func] = nil
end
function callback(name, ...)
if not (name and intercepts[name]) then return end
for func, _ in pairs(intercepts[name]) do
func(...)
end
end
-- MCM
-- Load the defaults
local function load_defaults()
local t = {}
local op = weapon_cover_tilt_mcm.op
for i, v in ipairs(op.gr) do
if v.def ~= nil then
t[v.id] = v.def
end
end
return t
end
local settings = load_defaults()
local function load_settings()
settings = load_defaults()
if ui_mcm then
for k, v in pairs(settings) do
settings[k] = ui_mcm.get("weapon_cover_tilt/" .. k)
end
end
end
function get_setting(key)
return settings[key]
end
function debug_enabled()
return DEV_DEBUG or DEV_DEBUG_DEV
end
local parameters = {
-- Type: 0 = string | 1 = number | 2 = 3d vector | 3 = 4d vector |
["hands_position"] = { name = "Hands Position", typ = 2, def = {0,0,0}, indx = 1, min = -180, max = 180, step = 0.0001, idxa = 0, idxb = 0, hud = true },
["hands_orientation"] = { name = "Hands Orientation", typ = 2, def = {0,0,0}, indx = 2, min = -180, max = 180, step = 0.0001, idxa = 1, idxb = 0, hud = true },
["aim_hud_offset_pos"] = { name = "Aim Position", typ = 2, def = {0,0,0}, indx = 3, min = -180, max = 180, step = 0.0001, idxa = 0, idxb = 1, hud = true },
["aim_hud_offset_rot"] = { name = "Aim Orientation", typ = 2, def = {0,0,0}, indx = 4, min = -180, max = 180, step = 0.0001, idxa = 1, idxb = 1, hud = true },
["gl_hud_offset_pos"] = { name = "GL Position", typ = 2, def = {0,0,0}, indx = 5, min = -180, max = 180, step = 0.0001, idxa = 0, idxb = 2, hud = true },
["gl_hud_offset_rot"] = { name = "GL Orientation", typ = 2, def = {0,0,0}, indx = 6, min = -180, max = 180, step = 0.0001, idxa = 1, idxb = 2, hud = true },
["aim_hud_offset_alt_pos"] = { name = "Alt Position", typ = 2, def = {0,0,0}, indx = 7, min = -180, max = 180, step = 0.0001, idxa = 0, idxb = 3, hud = true },
["aim_hud_offset_alt_rot"] = { name = "Alt Orientation", typ = 2, def = {0,0,0}, indx = 8, min = -180, max = 180, step = 0.0001, idxa = 1, idxb = 3, hud = true },
["lowered_hud_offset_pos"] = { name = "Lowered Position", typ = 2, def = {0,0,0}, indx = 9, min = -180, max = 180, step = 0.0001, idxa = 0, idxb = 4, hud = true },
["lowered_hud_offset_rot"] = { name = "Lowered Orientation", typ = 2, def = {0,0,0}, indx = 10, min = -180, max = 180, step = 0.0001, idxa = 1, idxb = 4, hud = true },
["fire_point"] = { name = "Fire Point", typ = 2, def = {0,0,0}, indx = 11, min = -180, max = 180, step = 0.0001, idxa = 0, idxb = 10, hud = true, no_16x9 = true },
["fire_point2"] = { name = "Fire Point 2", typ = 2, def = {0,0,0}, indx = 12, min = -180, max = 180, step = 0.0001, idxa = 0, idxb = 11, hud = true, no_16x9 = true },
["fire_direction"] = { name = "Fire Direction", typ = 2, def = {0,0,1}, indx = 13, min = -180, max = 180, step = 0.0001, idxa = 1, idxb = 10, hud = true, no_16x9 = true },
["shell_point"] = { name = "Shell Point", typ = 2, def = {0,0,0}, indx = 14, min = -180, max = 180, step = 0.0001, idxa = 1, idxb = 11, hud = true, no_16x9 = true },
["custom_ui_pos"] = { name = "UI Position", typ = 2, def = {0,0,0}, indx = 15, min = -180, max = 180, step = 0.0001, idxa = 0, idxb = 20 }, --idxb 20 is reserved for device ui
["custom_ui_rot"] = { name = "UI Orientation", typ = 2, def = {0,0,0}, indx = 16, min = -180, max = 180, step = 1, idxa = 1, idxb = 20 },
["item_position"] = { name = "Item Position", typ = 2, def = {0,0,0}, indx = 17, min = -180, max = 180, step = 0.0001, idxa = 0, idxb = 12, hud = true, no_16x9 = true },
["item_orientation"] = { name = "Item Orientation", typ = 2, def = {0,0,0}, indx = 18, min = -180, max = 180, step = 0.0001, idxa = 1, idxb = 12, hud = true, no_16x9 = true },
["scope_zoom_factor"] = { name = "Zoom Factor", typ = 1, def = 0, indx = 19, min = 0, max = 120, step = 0.1 },
["gl_zoom_factor"] = { name = "GL Zoom Factor", typ = 1, def = 0, indx = 20, min = 0, max = 120, step = 0.1 },
["scope_zoom_factor_alt"] = { name = "Alt Zoom Factor", typ = 1, def = 0, indx = 21, min = 0, max = 120, step = 0.1 },
}
function reset_wpn_hud(sec)
local hud_sec = SYS_GetParam(0, sec, "hud")
if not hud_sec then return end
hud_adjust.enabled(true)
for k, v in pairs(parameters) do
if v.typ == 1 then
local p = SYS_GetParam(2, hud_sec, k, v.def)
if p then
hud_adjust.set_value(k, p)
end
elseif v.typ == 2 then
local function res(str)
local str = str or ""
local p = SYS_GetParam(0, hud_sec, v.no_16x9 and k or (k .. str))
if not p then
p = table.concat(v.def, ",")
end
if p then
p = str_explode(p, ",")
for i, d in ipairs(v.def) do
p[i] = p[i] and tonumber(p[i]) or d
end
hud_adjust.set_vector(v.idxa, v.idxb, p[1] or 0, p[2] or 0, p[3] or 0)
end
end
res(utils_xml.is_widescreen() and "_16x9")
end
end
hud_adjust.enabled(false)
end
function IsBinoc(sec)
return SYS_GetParam(0, sec, "ammo_class", "") == "ammo_binoc"
or SYS_GetParam(0, sec, "class", "") == "WP_BINOC"
end
function IsThrowable(wpn)
return IsBolt(wpn) or SYS_GetParam(0, wpn:section(), "class", "") == "II_BOLT"
end
local scoped_weapon_zoomed = false
function isScopedWeapon(wpn)
if not wpn then return false end
local has_scopes_sect = SYS_GetParam(0, wpn:section(), "scopes_sect", "") ~= ""
local has_texture = SYS_GetParam(0, wpn:section(), "scope_texture", "") ~= ""
local cobj = wpn:cast_Weapon()
-- If old scopes system
if has_scopes_sect then
if cobj and cobj:IsScopeAttached() or wpn:weapon_is_scope() then
return true
end
else
-- If there is fixed scope or no scope defined, but also if there is a scope texture, it will use the texture for zoomin and considered a scoped weapon
if (wpn:weapon_scope_status() == 1 or wpn:weapon_scope_status() == 0) and has_texture then
return true
end
-- Usual check
if (cobj and cobj:IsScopeAttached() or wpn:weapon_is_scope()) and has_texture then
return true
end
end
return false
end
function isSilencedWeapon(wpn)
if not wpn then return false end
local cobj = wpn:cast_Weapon()
return wpn:weapon_silencer_status() == 2
and (cobj and cobj:IsSilencerAttached() or wpn:weapon_is_silencer() or utils_item.addon_attached(wpn, "sl"))
end
local random_funcs = demonized_randomizing_functions
local wpn_positions = weapon_cover_tilt_positions or {}
local wpn_radii = weapon_cover_tilt_gun_trigger_radii and weapon_cover_tilt_gun_trigger_radii.weapon_trigger_radii or {}
function getBestMatch(sec)
local best_match, best_match_l = nil, 0
local function getMatch(k, s1, s2)
local a, b = s1:find(s2)
if a and b then
local l = b - a + 1
if l > best_match_l then
best_match = k
best_match_l = l
end
end
end
for k, v in pairs(wpn_radii) do
getMatch(k, sec, k)
-- getMatch(k, k, sec)
end
return best_match
end
-- Test of is material of static geometry is shootable, engine edit required
--[[
Material List
flBreakable = (1ul << 0ul),
flBounceable = (1ul << 2ul),
flSkidmark = (1ul << 3ul),
flBloodmark = (1ul << 4ul),
flClimable = (1ul << 5ul),
flPassable = (1ul << 7ul),
flDynamic = (1ul << 8ul),
flLiquid = (1ul << 9ul),
flSuppressShadows = (1ul << 10ul),
flSuppressWallmarks = (1ul << 11ul),
flActorObstacle = (1ul << 12ul),
flNoRicoshet = (1ul << 13ul),
flInjurious = (1ul << 28ul),
flShootable = (1ul << 29ul),
flTransparent = (1ul << 30ul),
flSlowDown = (1ul << 31ul)
// material exports
.def_readonly("material_name", &script_rq_result::pMaterialName)
.def_readonly("material_flags", &script_rq_result::pMaterialFlags)
.def_readonly("material_phfriction", &script_rq_result::fPHFriction)
.def_readonly("material_phdamping", &script_rq_result::fPHDamping)
.def_readonly("material_phspring", &script_rq_result::fPHSpring)
.def_readonly("material_phbounce_start_velocity", &script_rq_result::fPHBounceStartVelocity)
.def_readonly("material_phbouncing", &script_rq_result::fPHBouncing)
.def_readonly("material_flotation_factor", &script_rq_result::fFlotationFactor)
.def_readonly("material_shoot_factor", &script_rq_result::fShootFactor)
.def_readonly("material_shoot_factor_mp", &script_rq_result::fShootFactorMP)
.def_readonly("material_bounce_damage_factor", &script_rq_result::fBounceDamageFactor)
.def_readonly("material_injurious_speed", &script_rq_result::fInjuriousSpeed)
.def_readonly("material_vis_transparency_factor", &script_rq_result::fVisTransparencyFactor)
.def_readonly("material_snd_occlusion_factor", &script_rq_result::fSndOcclusionFactor)
.def_readonly("material_density_factor", &script_rq_result::fDensityFactor)
--]]
local function lshift(x, by)
return x * 2 ^ by
end
local function test(x, mask)
return bit_and(x, mask) == mask
end
local flags_test = {
-- {"flBreakable", lshift(1, 0)},
-- {"flBounceable", lshift(1, 2)},
-- {"flSkidmark", lshift(1, 3)},
-- {"flBloodmark", lshift(1, 4)},
-- {"flClimable", lshift(1, 5)},
-- {"flPassable", lshift(1, 7)},
-- {"flDynamic", lshift(1, 8)},
-- {"flLiquid", lshift(1, 9)},
-- {"flSuppressShadows", lshift(1, 10)},
-- {"flSuppressWallmarks", lshift(1, 11)},
-- {"flActorObstacle", lshift(1, 12)},
-- {"flNoRicoshet", lshift(1, 13)},
-- {"flInjurious", lshift(1, 28)},
{"flShootable", lshift(1, 29)},
-- {"flTransparent", lshift(1, 30)},
-- {"flSlowDown", lshift(1, 31)},
}
function isShootableMaterial(max_dist)
local max_dist = max_dist or 10
local ray = ray_pick()
ray:set_flags(3)
ray:set_range(max_dist)
ray:set_position(device().cam_pos)
ray:set_direction(device().cam_dir)
ray:set_ignore_object(db.actor)
local res = ray:query()
-- Return false if failed query
if not res then
return false
end
-- Return false if game object
if ray:get_object() then
return false
end
local result = ray:get_result()
-- Return false if engine doesnt have material exports or unknown material
if not (
result
and result.material_name
and result.material_flags
and result.material_shoot_factor
)
then
return false
end
local flags = result.material_flags
local name = result.material_name
local shoot_factor = result.material_shoot_factor
-- Test material flags
local flags_test_result = {}
for i, v in ipairs(flags_test) do
if test(flags, v[2]) then
flags_test_result[v[1]] = true
end
-- printf("%s, %s test, %s, %s, %s", name, v[1], flags, v[2], test(flags, v[2]))
end
-- printf("%s, material_shoot_factor %s", name, shoot_factor)
-- If name contains "bush" or material is shootable and low shoot_factor - true
if string.find(name, "bush") then
return true
end
if string.find(name, "water") then
return false
end
if false
-- or (flags_test_result.flShootable and shoot_factor <= 0.01)
then
return true
end
return false
end
-- Sections of weapons that have kind "pistol" but they arent pistols
not_pistol_sec = {
wpn_svt40_short = true,
wpn_avt40_short = true,
wpn_aek919k = true,
}
-- Adjust gun radii by kind if cant find section in radii table
wpn_kind_adjustment_table = {
w_pistol = -0.3,
w_smg = -0.12,
w_sniper = 0.12,
w_shotgun = -0.05,
w_rifle = 0,
}
local weapon_table = {}
function add_to_weapon_table(sec, force)
if weapon_table[sec] and not force then return end
weapon_table[sec] = {
hands_position = (function()
local hud_sec = SYS_GetParam(0, sec, "hud")
local c = str_explode(utils_xml.is_widescreen() and SYS_GetParam(0, hud_sec, "hands_position_16x9") or SYS_GetParam(0, hud_sec, "hands_position") or "0,0,0", ",")
return {
[0] = tonumber(c[1]) or 0,
[1] = tonumber(c[2]) or 0,
[2] = tonumber(c[3]) or 0,
}
end)(),
hands_orientation = (function()
local hud_sec = SYS_GetParam(0, sec, "hud")
local c = str_explode(utils_xml.is_widescreen() and SYS_GetParam(0, hud_sec, "hands_orientation_16x9") or SYS_GetParam(0, hud_sec, "hands_orientation") or "0,0,0", ",")
return {
[0] = tonumber(c[1]) or 0,
[1] = tonumber(c[2]) or 0,
[2] = tonumber(c[3]) or 0,
}
end)(),
inv_weight = SYS_GetParam(2, sec, "inv_weight", 0),
zoom_rotate_time = SYS_GetParam(2, sec, "zoom_rotate_time", 0.25),
}
end
local target_pos = {
hands_position_pistol = {
[0] = -0.050912,
[1] = -0.646908,
[2] = 0.280633,
},
hands_position_rifle = {
[0] = 0.055224,
[1] = -0.6,
[2] = 0.202598,
}
}
local custom_offsets = {
[0] = 0,
[1] = 0,
[2] = 0,
}
function set_custom_offsets(x, y, z)
custom_offsets[0] = x or 0
custom_offsets[1] = y or 0
custom_offsets[2] = z or 0
end
local tilt_enabled = false
local tilt_key_pressed = false
local firepos_state = 0
local yaw
local roll
local max_deg = 75
local grace_threshold = 75
local grace_time = 0
function set_yaw(deg)
yaw = deg
end
function set_roll(deg)
roll = deg
end
function is_tilt_enabled()
return tilt_enabled
end
function is_tilt_key_pressed()
return tilt_key_pressed
end
function randomize_roll_yaw()
if not yaw then
local max_yaw = settings.yaw_variation
yaw = random_float(-max_yaw, max_yaw)
end
if not roll then
local max_roll = settings.roll_variation
roll = random_float(-max_roll, max_roll * 0.33)
end
end
function remove_roll_yaw()
yaw = nil
roll = nil
end
function enable_tilt(sec, wpn)
if scoped_weapon_zoomed then
if (get_console():get_bool("wpn_aim_toggle")) then
level.press_action(bind_to_dik(key_bindings.kWPN_ZOOM))
else
level.release_action(bind_to_dik(key_bindings.kWPN_ZOOM))
end
end
if not tilt_enabled then
-- Dirty AF hack to reset position (unused)
-- local wpn_hud = ui_debug_wpn_hud.WpnHudEditor(nil, sec)
-- if wpn_hud then
-- wpn_hud:Reset(true)
-- wpn_hud:Close()
-- end
reset_wpn_hud(sec)
hud_adjust.enabled(true)
set_weapon_position(sec, weapon_table[sec].hands_position, weapon_table[sec].hands_orientation)
if debug_enabled() then
exec_console_cmd("g_firepos 1")
end
tilt_enabled = true
callback("actor_on_weapon_tilt_start", wpn)
end
end
function reset(sec)
if tilt_enabled then
if sec and weapon_table[sec] then reset_wpn_hud(sec) end
hud_adjust.enabled(false)
if debug_enabled() then
exec_console_cmd("g_firepos " .. (firepos_state or 0))
end
smoothed_values.hands_position_x = nil
smoothed_values.hands_position_y = nil
smoothed_values.hands_position_z = nil
smoothed_values.hands_orientation_x = nil
smoothed_values.hands_orientation_y = nil
smoothed_values.hands_orientation_z = nil
smoothed_values.coeff = nil
tilt_enabled = false
tilt_key_pressed = false
grace_time = 0
callback("actor_on_weapon_tilt_end", wpn)
end
end
-- Get steps depending on inv_weight, heavier guns have less movement speed
function get_weapon_weight(wpn, sec)
local cobj = wpn and wpn:cast_Weapon()
local weight = clamp(cobj and cobj:Weight() or (weapon_table[sec] and weapon_table[sec].inv_weight or 0), 0, 20)
return weight
end
function get_weapon_steps(wpn, sec)
return settings.animation_speed * (1 + get_weapon_weight(wpn, sec) * settings.animation_weight_coeff * 0.07)
end
function set_weapon_position(sec, new_pos, new_ori)
-- printf("old_pos for %s: %s, %s, %s", sec, weapon_table[sec].hands_position[0], weapon_table[sec].hands_position[1], weapon_table[sec].hands_position[2])
-- printf("new_pos for %s: %s, %s, %s", sec, new_pos[0], new_pos[1], new_pos[2])
-- printf("old_ori for %s: %s, %s, %s", sec, weapon_table[sec].hands_orientation[0], weapon_table[sec].hands_orientation[1], weapon_table[sec].hands_orientation[2])
-- printf("new_pri for %s: %s, %s, %s", sec, new_ori[0], new_ori[1], new_ori[2])
hud_adjust.set_vector(0, 0, new_pos[0] or 0, new_pos[1] or 0, new_pos[2] or 0)
hud_adjust.set_vector(1, 0, new_ori[0] or 0, new_ori[1] or 0, new_ori[2] or 0)
-- hud_adjust.set_vector(parameters[parent].idxa, parameters[parent].idxb, value_1, value_2, value_3)
end
function soft_reset(wpn, sec, delta)
if not weapon_table[sec] then return reset(sec) end
local delta = delta or device().time_delta
if tilt_enabled then
-- k is modifier for ema steps to have more inertia in the beginning of movement
local k = max(1, 1.5 - (1 - normalize(smoothed_values.hands_orientation_y or max_deg, weapon_table[sec].hands_orientation[1], max_deg)))
-- Increase speed of return to position when closer to it for zoomed weapons
local is_scoped = isScopedWeapon(wpn)
local scope_magnitude = 0.105
k = k * (is_scoped and normalize(smoothed_values.hands_orientation_y, weapon_table[sec].hands_orientation[1], max_deg) ^ scope_magnitude or 1)
-- printf("cur %s, min %s, max %s, %s", smoothed_values.hands_orientation_y, weapon_table[sec].hands_orientation[1], max_deg, k)
local steps = get_weapon_steps(wpn, sec) * k
-- Smooth the coeff
local coeff_smoothing = steps * 0.5
local coeff = ema("coeff", 0, 0, coeff_smoothing, delta)
-- Remove random roll and yaw if close to end
if coeff < 0.5 then
remove_roll_yaw()
end
local new_pos = {
[0] = ema("hands_position_x", lerp(weapon_table[sec].hands_position[0], smoothed_values.hands_position_x, coeff), weapon_table[sec].hands_position[0], steps, delta),
[1] = ema("hands_position_y", lerp(weapon_table[sec].hands_position[1], smoothed_values.hands_position_y, coeff), weapon_table[sec].hands_position[1], steps, delta),
[2] = ema("hands_position_z", lerp(weapon_table[sec].hands_position[2], smoothed_values.hands_position_z, coeff), weapon_table[sec].hands_position[2], steps, delta),
}
local new_ori = {
[0] = ema("hands_orientation_x", lerp(weapon_table[sec].hands_orientation[0], smoothed_values.hands_orientation_x, coeff), weapon_table[sec].hands_orientation[0], steps, delta),
[1] = ema("hands_orientation_y", lerp(weapon_table[sec].hands_orientation[1], smoothed_values.hands_orientation_y, coeff), weapon_table[sec].hands_orientation[1], steps, delta),
[2] = ema("hands_orientation_z", lerp(weapon_table[sec].hands_orientation[2], smoothed_values.hands_orientation_z, coeff), weapon_table[sec].hands_orientation[2], steps, delta),
}
local animation_progress = normalize(new_ori[1], weapon_table[sec].hands_orientation[1], max_deg)
callback("actor_on_weapon_tilting_back", wpn, animation_progress)
set_weapon_position(sec, new_pos, new_ori)
if scoped_weapon_zoomed then
-- Calculate remaining time for scope aiming
local ms = delta / 1000
local t = 0
local threshold = 0.0007
smoothed_values.t = animation_progress
while t < weapon_table[sec].zoom_rotate_time and smoothed_values.t > threshold do
local k = smoothed_values.t ^ scope_magnitude
local steps = get_weapon_steps(wpn, sec) * k
local old = smoothed_values.t
smoothed_values.t = ema("t", 0, 0, steps, delta)
t = t + ms
end
local res = smoothed_values.t
smoothed_values.t = nil
if res > threshold then
if (get_console():get_bool("wpn_aim_toggle")) then
level.press_action(bind_to_dik(key_bindings.kWPN_ZOOM))
else
level.release_action(bind_to_dik(key_bindings.kWPN_ZOOM))
end
end
end
for i = 0, 2, 1 do
if abs(new_pos[i] - weapon_table[sec].hands_position[i]) > 0.055
or abs(new_ori[i] - weapon_table[sec].hands_orientation[i]) > 0.055
then
return
end
end
-- printf("soft reset complete")
-- printf("animation_progress %s", animation_progress)
reset(sec)
end
end
function calculate_new_position(wpn, sec, coeff, delta, steps)
-- Calculate new position
local position_coeff_y = -0.445
local position_coeff_z = 0.565
local delta = delta or device().time_delta
-- Get steps depending on inv_weight, heavier guns have less movement speed
-- k is modifier for ema steps to have more inertia in the beginning of movement
if not steps then
local k = max(1, 1.5 - sqrt(normalize(smoothed_values.hands_orientation_y or weapon_table[sec].hands_orientation[1], weapon_table[sec].hands_orientation[1], max_deg)))
steps = get_weapon_steps(wpn, sec) * k
end
-- Smooth the coeff
local coeff_smoothing = steps * 0.5
coeff = ema("coeff", coeff, 0, coeff_smoothing, delta)
local is_pistol = SYS_GetParam(0, sec, "kind", "") == "w_pistol" and not not_pistol_sec[sec]
local new_pos
if wpn_positions.weapon_positions[sec] then
local p = wpn_positions.weapon_positions[sec]
new_pos = {
[0] = lerp(weapon_table[sec].hands_position[0], p.x or weapon_table[sec].hands_position[0], coeff),
[1] = lerp(weapon_table[sec].hands_position[1], p.y or weapon_table[sec].hands_position[1], coeff),
[2] = lerp(weapon_table[sec].hands_position[2], p.z or weapon_table[sec].hands_position[2], coeff ^ 3.5),
}
else
-- Check for scopes on weapons
if not wpn_positions.weapon_offsets[sec] then
local parent = SYS_GetParam(0, sec, "parent_section", sec)
local scopes = str_explode(SYS_GetParam(0, parent, "scopes", ""), ",")
for k, v in pairs(scopes) do
if (parent .. "_" .. v) == sec then
wpn_positions.weapon_offsets[sec] = wpn_positions.weapon_offsets[parent]
break
end
end
-- Add table with 0 offsets if no weapon offset found
if not wpn_positions.weapon_offsets[sec] then
wpn_positions.weapon_offsets[sec] = {
x = 0,
y = 0,
z = 0
}
end
end
local o = {
[0] = wpn_positions.weapon_offsets[sec].x or 0,
[1] = wpn_positions.weapon_offsets[sec].y or 0,
[2] = wpn_positions.weapon_offsets[sec].z or 0,
}
-- Interpolate between offseted position and desirable closer to obstacle, looks better, mostly
-- Different calculations for pistols
if is_pistol then
local target_pos = {
[0] = weapon_table[sec].hands_position[0] + settings.offset_x + custom_offsets[0],
[1] = target_pos.hands_position_pistol[1] + settings.offset_y + custom_offsets[1],
[2] = target_pos.hands_position_pistol[2] + settings.offset_z + custom_offsets[2],
}
-- local a = {
-- [0] = weapon_table[sec].hands_position[0] + o[0] * coeff,
-- [1] = weapon_table[sec].hands_position[1] + (o[1] + position_coeff_y) * coeff,
-- [2] = weapon_table[sec].hands_position[2] + (o[2] + position_coeff_z) * coeff >= 1 and coeff or coeff ^ 2,
-- }
local b = {
[0] = lerp(weapon_table[sec].hands_position[0], (o[0] + target_pos[0]), coeff),
[1] = lerp(weapon_table[sec].hands_position[1], (o[1] + target_pos[1]), coeff),
[2] = lerp(weapon_table[sec].hands_position[2], (o[2] + target_pos[2]), coeff >= 1 and coeff or coeff ^ 2),
}
-- local c = {
-- [0] = lerp(b[0], b[0], coeff),
-- [1] = lerp(b[1], b[1], coeff),
-- [2] = lerp(b[2], b[2], coeff ^ 2),
-- }
new_pos = b
else
local target_pos = {
[0] = weapon_table[sec].hands_position[0] + settings.offset_x + custom_offsets[0],
[1] = target_pos.hands_position_rifle[1] + settings.offset_y + custom_offsets[1],
[2] = target_pos.hands_position_rifle[2] + settings.offset_z + custom_offsets[2],
}
local a = {
[0] = weapon_table[sec].hands_position[0] + o[0] * coeff,
[1] = weapon_table[sec].hands_position[1] + (o[1] + position_coeff_y) * coeff,
[2] = weapon_table[sec].hands_position[2] + (o[2] + position_coeff_z) * (coeff >= 1 and coeff or coeff ^ 3.5),
}
local b = {
[0] = lerp(weapon_table[sec].hands_position[0], (o[0] + target_pos[0]), coeff),
[1] = lerp(weapon_table[sec].hands_position[1], (o[1] + target_pos[1]), coeff),
[2] = lerp(weapon_table[sec].hands_position[2], (o[2] + target_pos[2]), coeff >= 1 and coeff or coeff ^ 3.5),
}
local c = {
[0] = lerp(a[0], b[0], coeff),
[1] = lerp(a[1], b[1], coeff),
[2] = lerp(a[2], b[2], coeff >= 1 and coeff or coeff ^ 3.5),
}
new_pos = c
end
end
-- Smooth new position
new_pos = {
[0] = ema("hands_position_x", new_pos[0], weapon_table[sec].hands_position[0], steps, delta),
[1] = ema("hands_position_y", new_pos[1], weapon_table[sec].hands_position[1], steps, delta),
[2] = ema("hands_position_z", new_pos[2], weapon_table[sec].hands_position[2], steps, delta),
}
-- Randomize roll and yaw
randomize_roll_yaw()
local new_ori = {
[0] = ema("hands_orientation_x", weapon_table[sec].hands_orientation[0] + yaw * coeff, weapon_table[sec].hands_orientation[0], steps, delta),
[1] = ema("hands_orientation_y", weapon_table[sec].hands_orientation[1] + max_deg * coeff, weapon_table[sec].hands_orientation[1], steps, delta),
[2] = ema("hands_orientation_z", weapon_table[sec].hands_orientation[2] + roll * coeff, weapon_table[sec].hands_orientation[2], steps, delta),
}
return new_pos, new_ori
end
local force_disabled = false
function set_force_disabled(v)
force_disabled = v or v == nil
end
local reset_on_show_done = false
function actor_on_update(binder, delta)
if force_disabled then return end
local actor = db.actor
-- Cancel if PDA is active
if actor:active_slot() == 8 or actor:active_slot() == 14 then
return reset()
end
local wpn = actor:active_item()
-- printf("%s", delta)
-- Reset if no wpn
if not wpn then
return reset()
end
local sec = wpn:section()
local hud_sec = SYS_GetParam(0, sec, "hud")
-- Reset when no hud sec
if not hud_sec then
return reset(sec)
end
-- Reset if melee weapon or binocs
if IsMelee(wpn) or IsItem("fake_ammo_wpn", sec) or IsBinoc(sec) then
return reset(sec)
end
-- Reset if throwable weapon (grenade, bolt)
if IsBolt(wpn) or IsGrenade(wpn) or IsThrowable(wpn) then
return reset(sec)
end
-- Add to weapon table
if not weapon_table[sec] then
add_to_weapon_table(sec)
end
-- Reset HUD once on weapon raise
local state = wpn:get_state()
if state == 1 then
if not reset_on_show_done then
-- printf("reset on show for %s", sec)
reset_on_show_done = true
reset(sec)
end
else
reset_on_show_done = false
end
-- Reset if callback function set enabled to false
local flags = {
enabled = true
}
callback("actor_on_weapon_before_tilt", wpn, flags)
if not flags.enabled then
return reset(sec)
end
-- Soft reset if weapon is lowered
if actor_weapon_lowered() then
return soft_reset(wpn, sec, delta)
end
-- Cancel if detector is active
if actor:active_detector() then
return soft_reset(wpn, sec, delta)
end
-- Soft reset if hiding weapon or reloading
if state == 2 or state == 7 then
return soft_reset(wpn, sec, delta)
end
local new_pos, new_ori
if tilt_key_pressed then
new_pos, new_ori = calculate_new_position(wpn, sec, 1, delta)
else
-- Get max dist based on weapon
-- Check for scopes on weapons
if not wpn_radii[sec] then
local parent = SYS_GetParam(0, sec, "parent_section", sec)
local scopes = str_explode(SYS_GetParam(0, parent, "scopes", ""), ",")
for k, v in pairs(scopes) do
if (parent .. "_" .. v) == sec then
wpn_radii[sec] = wpn_radii[parent]
break
end
end
if not wpn_radii[sec] then
local best_match = getBestMatch(sec)
if best_match then
wpn_radii[sec] = wpn_radii[best_match]
end
end
-- Adjust by weapon kind
if not wpn_radii[sec] then
local wpn_kind = SYS_GetParam(0, sec, "kind", "")
local wpn_kind_adjustment = wpn_kind_adjustment_table[wpn_kind] or 0
wpn_radii[sec] = wpn_kind_adjustment
end
if not wpn_radii[sec] then
wpn_radii[sec] = 0
end
end
local max_dist = settings.trigger_radius
-- Adjust distance
local dist_adjustment = (
-- Adjust distance based on wpn_radii table
(wpn_radii[sec] or 0)
-- Adjust distance based on silenced weapon
+ (settings.consider_silencer and isSilencedWeapon(wpn) and 0.12 or 0)
)
-- Magnify the adjustment
* settings.trigger_radius_magnitude
-- Adjust the distance
max_dist = max_dist + dist_adjustment
-- Get target dist
local dist = max(0, get_target_dist() - 0.5)
-- printf("target_dist %s", dist)
-- Soft reset if distance is more than max_dist
if dist > max_dist then
return soft_reset(wpn, sec, delta)
end
-- Soft reset if target is enemy stalker or monster
local target_obj = get_target_obj()
if target_obj and (IsMonster(target_obj) or (IsStalker(target_obj) and xr_combat_ignore.is_enemy(target_obj, actor, true))) then
return soft_reset(wpn, sec, delta)
end
-- Soft reset is material is shootable, engine edit required
if isShootableMaterial() then
return soft_reset(wpn, sec, delta)
end
-- Don't raise before grace time expired
-- if grace_time < grace_threshold then
-- grace_time = grace_time + device().time_delta
-- return
-- end
-- Apply non linear coefficient
local coeff = random_funcs.CircularEaseOutPowered(1 - dist / max_dist, 0.65)
-- Calculate new position
new_pos, new_ori = calculate_new_position(wpn, sec, coeff, delta)
end
-- Adjust hud
enable_tilt(sec, wpn)
local animation_progress = normalize(new_ori[1], weapon_table[sec].hands_orientation[1], max_deg)
if animation_progress > 0 then
callback("actor_on_weapon_tilting", wpn, animation_progress)
end
set_weapon_position(sec, new_pos, new_ori)
end
function actor_on_weapon_zoom_in(obj)
scoped_weapon_zoomed = isScopedWeapon(obj)
end
function actor_on_weapon_zoom_out(obj)
scoped_weapon_zoomed = false
end
function on_key_press(dik)
if dik == settings.manual_tilt_key_bind then
tilt_key_pressed = not tilt_key_pressed
else
local bind = dik_to_bind(dik)
local kb = key_bindings
if bind == kb.kWPN_ZOOM
or bind == kb.kWPN_FIRE
then
-- Postpone on next tick, needed to not fire on raised state
dte.CreateTimeEvent("tilt_key_pressed_postpone", 0, 0, function()
tilt_key_pressed = false
return true
end)
end
end
end
function actor_on_weapon_before_fire(flags)
if tilt_key_pressed then
tilt_key_pressed = false
flags.ret_value = false
end
end
function reset_settings()
load_settings()
if settings.enable_manual_tilt then
RegisterScriptCallback("on_key_press", on_key_press)
else
tilt_key_pressed = false
UnregisterScriptCallback("on_key_press", on_key_press)
end
if settings.enabled then
RegisterScriptCallback("actor_on_update", actor_on_update)
else
local sec
if db.actor then
local wpn = db.actor:active_item()
sec = wpn and wpn:section()
end
reset(sec)
UnregisterScriptCallback("actor_on_update", actor_on_update)
end
end
function actor_on_first_update()
-- firepos_state = debug_enabled() and get_console_cmd(0, "g_firepos") or 0
firepos_state = 0
reset_settings()
end
-- Reset without parameter, for callbacks
function reset_func()
reset()
end
function on_game_start()
RegisterScriptCallback("on_option_change", reset_settings)
RegisterScriptCallback("actor_on_before_death", reset_func)
RegisterScriptCallback("actor_on_net_destroy", reset_func)
RegisterScriptCallback("on_before_level_changing", reset_func)
RegisterScriptCallback("actor_on_first_update", actor_on_first_update)
RegisterScriptCallback("actor_on_update", actor_on_update)
RegisterScriptCallback("actor_on_weapon_zoom_in", actor_on_weapon_zoom_in)
RegisterScriptCallback("actor_on_weapon_zoom_out", actor_on_weapon_zoom_out)
RegisterScriptCallback("actor_on_weapon_before_fire", actor_on_weapon_before_fire)
RegisterScriptCallback("on_key_press", on_key_press)
end
-- Patches
-- Safemode when weapon is raised manually
actor_is_safemode = xr_conditions.actor_is_safemode
xr_conditions.actor_is_safemode = function(actor, npc)
return actor_is_safemode(actor, npc) or (tilt_enabled and tilt_key_pressed)
end
function muzzle_pos()
local item = db.actor:active_item()
if not item then return end
local pos = utils_obj.safe_bone_pos(item, "muzzle")
return game.world2ui(pos, true)
end