Divergent/mods/Enhanced Recoil Revised/gamedata/scripts/grok_bo_enhanced_recoil.script

421 lines
11 KiB
Plaintext
Raw Normal View History

2024-03-17 20:18:03 -04:00
-- 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