421 lines
11 KiB
Plaintext
421 lines
11 KiB
Plaintext
|
-- 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 ini_eff = ini_file("plugins\\grok_bo_enhanced_recoil.ltx")
|
||
|
local ini_eff_og = ini_file("plugins\\actor_effects.ltx")
|
||
|
|
||
|
local wpn_fx = {}
|
||
|
local function parse_anims(curr_ini_eff, sec, add_default)
|
||
|
if not curr_ini_eff then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
if wpn_fx[sec] then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local sec_p = ini_sys:r_string_ex(sec,"parent_section") or sec
|
||
|
|
||
|
if wpn_fx[sec_p] and (sec ~= sec_p) then
|
||
|
wpn_fx[sec] = wpn_fx[sec_p]
|
||
|
|
||
|
elseif curr_ini_eff:section_exist(sec_p) then
|
||
|
if not (
|
||
|
curr_ini_eff:r_string_ex(sec_p,"s")
|
||
|
-- and curr_ini_eff:r_string_ex(sec_p,"r")
|
||
|
and curr_ini_eff:r_string_ex(sec_p,"e")
|
||
|
)
|
||
|
then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local tbl = {}
|
||
|
|
||
|
-- parse strength
|
||
|
tbl.s = {}
|
||
|
local s = curr_ini_eff:r_string_ex(sec_p,"s")
|
||
|
local _s = str_explode(s,",")
|
||
|
for i=1,#_s do
|
||
|
tbl.s[#tbl.s + 1] = tonumber(_s[i])
|
||
|
end
|
||
|
|
||
|
-- parse strength randomizer
|
||
|
tbl.r = curr_ini_eff:r_float_ex(sec_p,"r")
|
||
|
|
||
|
-- parse anm
|
||
|
tbl.e = {}
|
||
|
local e = curr_ini_eff:r_string_ex(sec_p,"e")
|
||
|
local _e = str_explode(e,",")
|
||
|
for i=1,#_e do
|
||
|
local __e = str_explode(_e[i],":")
|
||
|
for j=1,#__e do
|
||
|
if not ((i == 1 or i == 4) and j >= 2) then
|
||
|
__e[j] = tonumber(__e[j])
|
||
|
end
|
||
|
end
|
||
|
tbl.e[#tbl.e + 1] = __e
|
||
|
end
|
||
|
|
||
|
-- parse ppe
|
||
|
tbl.p = {}
|
||
|
local p = curr_ini_eff:r_string_ex(sec_p,"p")
|
||
|
if p and p ~= "" then
|
||
|
for k,v in string.gmatch(p, "([%w_%-%s%.]+)=([%w_%-%s%.]+)") do
|
||
|
tbl.p[k] = tonumber(v)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if (not wpn_fx[sec_p]) and (sec ~= sec_p) then
|
||
|
wpn_fx[sec_p] = tbl
|
||
|
end
|
||
|
|
||
|
wpn_fx[sec] = tbl
|
||
|
--utils_data.print_table(wpn_fx[sec])
|
||
|
|
||
|
else
|
||
|
wpn_fx[sec] = add_default and wpn_fx["wpn_aug"] or true
|
||
|
--printf("not recoil effect for [%s]", sec)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Recoil modifier table
|
||
|
-- Contains functions that return modifier coefficients (see example)
|
||
|
modifier_table = {
|
||
|
[1] = function(effect_power, wpn, wpn_fx)
|
||
|
return 1
|
||
|
end,
|
||
|
}
|
||
|
|
||
|
function modifier(effect_power, wpn)
|
||
|
if is_empty(modifier_table) then
|
||
|
return effect_power
|
||
|
end
|
||
|
for i, v in ipairs(modifier_table) do
|
||
|
effect_power = effect_power * (v(effect_power, wpn, wpn_fx[wpn:section()]) or 1)
|
||
|
end
|
||
|
return effect_power
|
||
|
end
|
||
|
|
||
|
-- Add modifier, modifier_func is function as in modifier_table, pos is position to add (optional)
|
||
|
function add_modifier(modifier_func, pos)
|
||
|
pos = pos and clamp(pos, 1, #modifier_table + 1) or (#modifier_table + 1)
|
||
|
table.insert(modifier_table, pos, modifier_func)
|
||
|
end
|
||
|
|
||
|
function remove_modifier(modifier_func)
|
||
|
for i = #modifier_table, 1, -1 do
|
||
|
local v = modifier_table[i]
|
||
|
if modifier_func == v then
|
||
|
table.remove(modifier_table, i)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function clamp_effect_power(effect_power)
|
||
|
return math.max(0.15, effect_power)
|
||
|
end
|
||
|
|
||
|
local effect_min_id = 5000
|
||
|
local effect_max_id = 30000
|
||
|
local effect_id = effect_min_id
|
||
|
function next_effect_id()
|
||
|
effect_id = effect_id + 1
|
||
|
if effect_id > effect_max_id then
|
||
|
effect_id = effect_min_id
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function add_cam_effector(filename, anim_id, cyclic, callback_str, cam_fov, hud, effect_power)
|
||
|
local current_id = effect_id
|
||
|
level.add_cam_effector(filename, effect_id, cyclic, callback_str, cam_fov, hud, effect_power)
|
||
|
next_effect_id()
|
||
|
return current_id
|
||
|
end
|
||
|
|
||
|
function add_pp_effector(filename, anim_id, cyclic)
|
||
|
local current_id = effect_id
|
||
|
level.add_pp_effector(filename, effect_id, cyclic)
|
||
|
next_effect_id()
|
||
|
return current_id
|
||
|
end
|
||
|
|
||
|
local shotc = 1
|
||
|
local shott = 0
|
||
|
local fov_r = 1
|
||
|
abakan_state = 0
|
||
|
function shoot_effect(sect)
|
||
|
--printf("-SHOOT")
|
||
|
|
||
|
if shott and (shott < time_global()) then
|
||
|
shotc = 1
|
||
|
shott = nil
|
||
|
end
|
||
|
|
||
|
local anims = {}
|
||
|
local s = wpn_fx[sect].s
|
||
|
local sc = #s
|
||
|
|
||
|
if sc > 1 then
|
||
|
if wpn_fx[sect].r == 1 then
|
||
|
s = s[math.random(#s)]
|
||
|
elseif wpn_fx[sect].r == 2 then
|
||
|
if shotc == 1 then
|
||
|
s = s[1]
|
||
|
else
|
||
|
s = s[math.random(2,#s)]
|
||
|
end
|
||
|
else
|
||
|
sc = shotc <= sc and shotc or (sc > 2 and math.random(sc-1,sc)) or sc
|
||
|
s = s[sc]
|
||
|
end
|
||
|
else
|
||
|
s = s[1]
|
||
|
end
|
||
|
-- local cnt, r = math.modf(s*fov_r)
|
||
|
-- if r ~=0 then
|
||
|
-- s = math.max(1, cnt - 1)
|
||
|
-- end
|
||
|
for i,v in ipairs(wpn_fx[sect].e) do
|
||
|
local cnt,r = math.modf(v[1]*fov_r)
|
||
|
if r ~= 0 and cnt > 4 then --fmb
|
||
|
cnt = cnt-cnt*math.random(0,r*100)/100 --fmb
|
||
|
cnt,r = math.modf(cnt)
|
||
|
end
|
||
|
|
||
|
if r ~= 0 and math.random() < r then
|
||
|
cnt = cnt+1
|
||
|
end
|
||
|
|
||
|
if cnt > 0 then
|
||
|
table.insert(anims,{e = i,d = v[2] or math.random(0,1),c = cnt})
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Get weapon upgrades and reduce effect based on them
|
||
|
local effect_power = settings.effect_power
|
||
|
local wpn = db.actor:active_item()
|
||
|
-- printf("base effect_power %s", effect_power)
|
||
|
wpn:iterate_installed_upgrades( function(upgr_sec)
|
||
|
-- printf("installed upgrade %s", upgr_sec)
|
||
|
local section = SYS_GetParam(0, upgr_sec, "section")
|
||
|
if not section then return end
|
||
|
|
||
|
local upgrades_power = settings.upgrades_power or 1
|
||
|
|
||
|
-- Upgrades reduces values, so effect power must be with "+"
|
||
|
local cam_dispersion = SYS_GetParam(0, section, "cam_dispersion")
|
||
|
cam_dispersion = cam_dispersion and tonumber(cam_dispersion) or 0
|
||
|
effect_power = effect_power + cam_dispersion * upgrades_power
|
||
|
|
||
|
local PDM_disp_base = SYS_GetParam(0, section, "PDM_disp_base")
|
||
|
PDM_disp_base = PDM_disp_base and tonumber(PDM_disp_base) or 0
|
||
|
effect_power = effect_power + PDM_disp_base * upgrades_power
|
||
|
|
||
|
effect_power = clamp_effect_power(effect_power)
|
||
|
end)
|
||
|
|
||
|
-- Get actor state and increase/reduce effect
|
||
|
-- Reduce recoil by 50% when proning
|
||
|
if IsMoveState('mcCrouch') and IsMoveState('mcAccel') then
|
||
|
effect_power = clamp_effect_power(effect_power * (1 - 0.5 * (settings.mcProne_power or 1)))
|
||
|
-- Reduce recoil by 15% when crouching
|
||
|
elseif IsMoveState('mcCrouch') then
|
||
|
effect_power = clamp_effect_power(effect_power * (1 - 0.15 * (settings.mcCrouch_power or 1)))
|
||
|
end
|
||
|
|
||
|
-- Increase recoil by 20% when moving
|
||
|
if IsMoveState('mcAnyMove') then
|
||
|
effect_power = clamp_effect_power(effect_power * (1 + 0.2 * (settings.mcAnyMove_power or 1)))
|
||
|
end
|
||
|
|
||
|
-- Increase recoil by 25% when sprinting
|
||
|
if IsMoveState('mcSprint') then
|
||
|
effect_power = clamp_effect_power(effect_power * (1 + 0.25 * (settings.mcSprint_power or 1)))
|
||
|
end
|
||
|
|
||
|
-- Increase recoil by 25% when jumping and landing
|
||
|
if IsMoveState('mcJump')
|
||
|
or IsMoveState('mcFall')
|
||
|
or IsMoveState('mcLanding')
|
||
|
or IsMoveState('mcLanding2') then
|
||
|
effect_power = clamp_effect_power(effect_power * (1 + 0.25 * (settings.mcJump_power or 1)))
|
||
|
end
|
||
|
|
||
|
-- Reduce recoil by 5% when zooming
|
||
|
if axr_main.weapon_is_zoomed then
|
||
|
effect_power = clamp_effect_power(effect_power * (1 - 0.05 * (settings.mcZoom_power or 1)))
|
||
|
end
|
||
|
|
||
|
-- Clamp result
|
||
|
effect_power = clamp_effect_power(effect_power)
|
||
|
|
||
|
-- Condition modifier
|
||
|
if settings.condition_power > 0 then
|
||
|
local c = wpn:condition()
|
||
|
local c_threshold = 0.93
|
||
|
local m
|
||
|
if c >= c_threshold then
|
||
|
m = lerp(0, -0.12, normalize(c, c_threshold, 1)) * settings.condition_power
|
||
|
else
|
||
|
m = lerp(0.85, 0, normalize(c, 0, c_threshold)) * settings.condition_power
|
||
|
end
|
||
|
effect_power = clamp_effect_power(effect_power * (1 + m))
|
||
|
end
|
||
|
|
||
|
-- External modifiers for recoil
|
||
|
effect_power = clamp_effect_power(modifier(effect_power, wpn))
|
||
|
|
||
|
-- Abakan delayed effect
|
||
|
if ( -- If weapon is Abakan
|
||
|
wpn:section():find("abakan")
|
||
|
or wpn:section():find("an_94")
|
||
|
or wpn:section():find("an94")
|
||
|
)
|
||
|
-- If Abakan has > 1 ammo or is in second shot state
|
||
|
and (wpn:cast_Weapon():GetAmmoElapsed() > 1 or abakan_state == 1)
|
||
|
-- If Abakan fire mode is not single shot
|
||
|
and wpn:cast_Weapon():GetFireMode() ~= 1
|
||
|
-- Check for 2 shot burst fire mode if "cycle_down" is false (vanilla behaviour, BAN's Abakan replacer works correctly to IRL)
|
||
|
and (SYS_GetParam(1, wpn:section(), "cycle_down") or wpn:cast_Weapon():GetFireMode() == 2)
|
||
|
then
|
||
|
-- First shot - 0 recoil
|
||
|
if abakan_state == 0 then
|
||
|
effect_power = 0.0000001
|
||
|
abakan_state = 1
|
||
|
-- Second shot - double recoil power
|
||
|
elseif abakan_state == 1 then
|
||
|
effect_power = effect_power * 2
|
||
|
abakan_state = 2
|
||
|
-- Consecutive shots - single recoil power
|
||
|
elseif abakan_state == 2 then
|
||
|
-- effect_power = effect_power
|
||
|
-- abakan_state = 2
|
||
|
end
|
||
|
|
||
|
-- Reset state after some time
|
||
|
-- Add small epsilon to timer for better behaviour on full auto consecutive shots
|
||
|
local state = "abakan_state_reset"
|
||
|
local state_id = "abakan_state_reset"
|
||
|
local timer = 1 / (SYS_GetParam(2, wpn:section(), "rpm") / 60) + 0.001
|
||
|
ResetTimeEvent(state, state_id, timer)
|
||
|
CreateTimeEvent(state, state_id, timer, function()
|
||
|
abakan_state = 0
|
||
|
return true
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
-- printf("new effect_power %s", effect_power)
|
||
|
|
||
|
for i,a in ipairs(anims) do
|
||
|
local n = string.format([[shoot\s%s_e%s_%s.anm]],s,a.e,a.d)
|
||
|
for ii=1,a.c do
|
||
|
local anim_id = 0
|
||
|
add_cam_effector(n,anim_id,false,"", 0, true, effect_power)
|
||
|
if settings.gun_effect_power > 0 then
|
||
|
add_cam_effector(n,anim_id + 1,false,"", 0, false, effect_power * 0.17 * settings.gun_effect_power)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
for k,v in pairs(wpn_fx[sect].p) do
|
||
|
local eid = 0
|
||
|
eid = add_pp_effector(string.format([[shoot\%s_s%s.ppe]],k,s), eid, false)
|
||
|
v = math.max(0.0000001, v * settings.pp_power)
|
||
|
level.set_pp_effector_factor(eid, v)
|
||
|
end
|
||
|
shotc = shotc+1
|
||
|
shott = time_global()+100
|
||
|
|
||
|
if settings.fov_changer then
|
||
|
local fov_before = get_console_cmd(2, "fov")
|
||
|
local weapon_shake_factor = s * 0.1 + 1
|
||
|
exec_console_cmd("fov " .. clamp(math.atan(math.tan(fov_before * (0.5 * math.pi / 180)) * (axr_main.weapon_is_zoomed and 1.1 or 1.05 * weapon_shake_factor ) * settings.fov_power) / (0.5 * math.pi / 180), 5, 120))
|
||
|
|
||
|
local function reset_fov_to_normal(fov_val)
|
||
|
exec_console_cmd("fov " .. fov_val)
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
CreateTimeEvent(0, "reset_fov_to_normal", 0.075, reset_fov_to_normal, fov_before)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
function Update_Shooting(obj, wpn, ammo_elapsed, grenade_elapsed, ammo_type, grenade_type)
|
||
|
if (obj:id() ~= AC_ID) then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local wpn_obj = utils_item.item_is_fa(wpn) and wpn
|
||
|
if wpn_obj then
|
||
|
local sec = wpn_obj:section()
|
||
|
|
||
|
if (not wpn_fx[sec]) then
|
||
|
parse_anims(ini_eff, sec, true)
|
||
|
parse_anims(ini_eff_og, sec, true)
|
||
|
end
|
||
|
|
||
|
if (wpn_fx[sec] == true) then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
shoot_effect(sec)
|
||
|
|
||
|
--printf("Shooting effect played for [%s]", sec)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- MCM
|
||
|
function load_defaults()
|
||
|
local t = {}
|
||
|
local op = grok_bo_enhanced_recoil_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
|
||
|
|
||
|
settings = load_defaults()
|
||
|
|
||
|
function load_settings()
|
||
|
settings = load_defaults()
|
||
|
if ui_mcm then
|
||
|
for k, v in pairs(settings) do
|
||
|
settings[k] = ui_mcm.get(grok_bo_enhanced_recoil_mcm.op_id .. "/" .. k)
|
||
|
end
|
||
|
end
|
||
|
return settings
|
||
|
end
|
||
|
|
||
|
function on_option_change()
|
||
|
load_settings()
|
||
|
if settings.enabled then
|
||
|
RegisterScriptCallback("actor_on_weapon_fired", Update_Shooting)
|
||
|
else
|
||
|
UnregisterScriptCallback("actor_on_weapon_fired", Update_Shooting)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function actor_on_first_update()
|
||
|
on_option_change()
|
||
|
ini_eff:section_for_each(function(sec)
|
||
|
parse_anims(ini_eff, sec)
|
||
|
end)
|
||
|
ini_eff_og:section_for_each(function(sec)
|
||
|
parse_anims(ini_eff_og, sec)
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
function on_game_start()
|
||
|
RegisterScriptCallback("on_option_change", on_option_change)
|
||
|
RegisterScriptCallback("actor_on_first_update", actor_on_first_update)
|
||
|
RegisterScriptCallback("actor_on_weapon_fired", Update_Shooting)
|
||
|
end
|