Divergent/mods/Anomaly Ballistics Overhaul/gamedata/scripts/enhanced_recoil_mcm.script

349 lines
9.9 KiB
Plaintext
Raw Normal View History

2024-03-17 20:18:03 -04:00
-- Rip of the enhanced recoil effects with wildcarding support and ammo support
-- get_config = ballistics_mcm.get_config
-- print_dbg = ballistics_mcm.print_dbg
local ini_eff = ini_file("plugins\\recoil\\importer.ltx")
-- track and upgrade recoil
local wpn_data = {
id = 0,
recoil = 1,
mult_table = {0, 0, 0, 0, 0, 0}
}
function actor_on_weapon_before_fire(flags)
local current_wpn = db.actor:active_item()
if not current_wpn or not (IsWeapon(current_wpn) and not IsItem("fake_ammo_wpn",sec)) then
wpn_data = {
id = 0,
recoil = 1,
mult_table = {0, 0, 0, 0, 0, 0}
}
return
end
-- cache recoil (determined by cam_step_angle_horz)
local id = current_wpn:id()
if wpn_data.id == id then return end
local sec = current_wpn:section()
local sec_p = ini_sys:r_string_ex(sec,"parent_section") or sec
local base_recoil = SYS_GetParam(2, sec, "cam_step_angle_horz")
local recoil_mod = 0
local upgrades = utils_item.get_upgrades_installed(current_wpn)
for _, upgrade in pairs(upgrades) do
local section = ini_sys:r_string_ex(upgrade, "section")
local recoil = ini_sys:r_string_ex(section,"cam_step_angle_horz")
if recoil then
local op = string.sub(recoil, 1, 1)
local val = tonumber(string.sub(recoil, 2))
recoil_mod = op == "+" and recoil_mod + val or recoil_mod - val
end
end
-- current_wpn:cast_Weapon():GetCurrentFireMode()
wpn_data.id = id
wpn_data.recoil = 1 + (recoil_mod/base_recoil)
wpn_data.mult_table = ini_eff:r_list(sec_p, "kick_recoil", "0, 0, 0, 0, 0, 0")
end
local wpn_fx = {}
local function parse_anims(sec)
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 ini_eff:section_exist(sec_p) then
local tbl = {}
-- parse strength
tbl.s = {}
local s = 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 = ini_eff:r_float_ex(sec_p,"r")
-- parse anm
tbl.e = {}
local e = 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
__e[j] = tonumber(__e[j])
end
tbl.e[#tbl.e + 1] = __e
end
-- parse ppe
tbl.p = {}
local p = 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
else
wpn_fx[sec] = true
end
end
local is_looking = false
local shotc = 1 -- Counts the number of shots in a consecutive burst
local shott = 0 -- Used to determin how far aparts shots can be to be considered part of the same burst
local kick_t = 0 -- Time when kick stops
local fov_r = 1
function shoot_effect(obj, sect)
if shott and (shott < time_global()) then
shotc = 1
shott = nil
end
local recoil_base = wpn_data.recoil or 1
-- update based on actor stance
if get_config("stance") then
if IsMoveState("mcAnyMove") then
recoil_base = recoil_base * 1.5
elseif IsMoveState("mcCrouch") then
recoil_base = recoil_base * (IsMoveState("mcAccel") and 0.25 or 0.5)
end
end
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
recoil_base = recoil_base * recoil_multiplier(obj, shotc)
recoil_anm(sect, recoil_base, s)
recoil_camera(obj, recoil_base, s)
shotc = shotc+1
shott = time_global()+200 -- adjust based on gun rpm / handling?
end
-- plays weapon animation, strength depends on recoil
function recoil_anm(sect, recoil_base, s)
local anims = {}
for i,v in pairs(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
for i,a in ipairs(anims) do
local n = string.format([[shoot\s%s_e%s_%s.anm]], s, 2, 0)
for ii=1,a.c do
-- printf("N: " .. n)
level.add_cam_effector(n,math.random(5000, 8000),false,"", 0, false, recoil_base*0.1)
end
-- printf("----")
end
for k,v in pairs(wpn_fx[sect].p) do
local eid = math.random(5000,8000)
level.add_pp_effector(string.format([[shoot\%s_s%s.ppe]],k,s), eid, false)
if v > 0 then
level.set_pp_effector_factor(eid,v)
end
end
end
function camera_effect(obj, recoil_base, h_recoil, strength)
-- Assumes h_recoil ranges from -1 to 1
local n = string.format([[shoot\s%s_e%s_%s.anm]], strength, 0, h_recoil < 0 and 0 or 1)
level.add_cam_effector(n,math.random(5000, 8000),false,"", 0, false, recoil_base*math.abs(h_recoil))
end
-- Invoked whenever a shot is fired and enhanced recoil applies.
-- Patch this to modify the magnitude based on the weapon and burst length
function recoil_multiplier(wpn, shotc)
return 1
end
local v_factor = 0.5
local v_recoil = 0
local h_factor = 0.4
local h_recoil = 0
-- consider stance, wpn weight
function recoil_camera(obj, recoil_base, s)
-- never execute if the functions are not defined
-- do not needlessly recompute if this is already firing - wait for time event to reset flag
if not (db.actor.actor_look_at_point and db.actor.actor_stop_look_at_point) or recoil_base < 1 or not get_config("camera") then return end
local sec = obj:section()
local sec_p = ini_sys:r_string_ex(sec,"parent_section") or sec
local mult_table = wpn_data.mult_table
-- after enough shots climbing stops, math.random will provide a little variance
local mult = tonumber(mult_table[shotc]) or (math.random()-0.5)/20
-- printf("Recoil mult is %s, shots fired %s", mult, shotc)
local actor_position = device().cam_pos
local actor_direction = device().cam_dir
local is_zoomed = obj:cast_Weapon():IsZoomed()
-- Horizontal
-- Base Horizontal Recoil
local horizontal_base = is_zoomed == 1 and SYS_GetParam(2, sec, "zoom_cam_step_angle_horz") or SYS_GetParam(2, sec, "cam_step_angle_horz")
horizontal_base = horizontal_base * (recoil_base)
-- Randomness
local h_recoil_rnd = (math.random()-0.5)
-- local h_recoil_rnd = math.random(-1, 1)
h_recoil = h_recoil_rnd * horizontal_base * h_factor
-- Rotate camera left-right
actor_direction = vector_rotate_y(actor_direction, h_recoil)
camera_effect(obj, recoil_base, h_recoil_rnd*2, s)
-- Vertical
local vertical_base = is_zoomed == 1 and SYS_GetParam(2, sec, "zoom_cam_dispersion") or SYS_GetParam(2, sec, "cam_dispersion")
vertical_base = vertical_base * (recoil_base)
local zoom_factor = obj:cast_Weapon():GetZoomFactor()
local zoom_kick_factor = is_zoomed and zoom_factor ~= 0 and 5*(1/zoom_factor) or 1
v_recoil = vertical_base * zoom_kick_factor * mult * v_factor
actor_direction.y = clamp(actor_direction.y + (v_recoil), -0.999, 0.999)
-- printf("recoil_base: " .. recoil_base)
kick_t = time_global()+(math.max(50, 100*recoil_base)) -- need to adjust for when scoped in
-- update the variables used below, don't reset if already firing
if is_looking then return end
-- force actor to look at new point of aim
db.actor:actor_look_at_point(vector():set(actor_position):add(actor_direction:mul(10000)))
is_looking = true
demonized_time_events.CreateTimeEvent("updatelook", 1, 0, function()
local active_item = db.actor:active_item()
if not active_item then
return true
end
-- if died for w/e reason stop immediately
if not is_looking then
db.actor:actor_stop_look_at_point()
return true
end
if active_item:get_state() == 5 then
db.actor:actor_stop_look_at_point()
if kick_t < time_global() then
is_looking = false
return true
end
actor_direction = device().cam_dir
actor_direction.y = clamp(actor_direction.y + (v_recoil), -0.999, 0.999)
actor_direction = vector_rotate_y(actor_direction, h_recoil)
db.actor:actor_look_at_point(vector():set(device().cam_pos):add(actor_direction:mul(10000)))
return false
end
is_looking = false
db.actor:actor_stop_look_at_point()
return true
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(sec)
end
if (wpn_fx[sec] == true) then
return
end
shoot_effect(wpn, sec)
end
end
OriginalShoot = actor_effects.shoot_effect
function actor_effects.shoot_effect(sec)
if get_config("enabled") then return end
OriginalShoot(sec)
end
function update_settings()
if get_config("enabled") then
RegisterScriptCallback("actor_on_weapon_fired",Update_Shooting)
RegisterScriptCallback("actor_on_weapon_before_fire", actor_on_weapon_before_fire)
else
UnregisterScriptCallback("actor_on_weapon_fired",Update_Shooting)
UnregisterScriptCallback("actor_on_weapon_before_fire", actor_on_weapon_before_fire)
end
end
-- If you don't use MCM, change your defaults from here.
local defaults = {
["enabled"] = true,
["camera"] = true,
["stance"] = true,
}
function get_config(key)
if ui_mcm then return ui_mcm.get("recoil/"..key) else return defaults[key] end
end
function on_mcm_load()
op = { id= "recoil",sh=true ,gr={
{ id= "title",type= "slide",link= "ui_options_slider_player",text="ui_mcm_recoil_title",size= {512,50},spacing= 20 },
{id = "enabled", type = "check", val = 1, def=true}, {id = "camera", type = "check", val = 1, def=true},
{id = "stance", type = "check", val = 1, def=true},
}
}
return op
end
function on_game_start()
RegisterScriptCallback("on_option_change",update_settings)
update_settings()
end