349 lines
9.9 KiB
Plaintext
349 lines
9.9 KiB
Plaintext
-- 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
|