Divergent/mods/Weapon Parts Overhaul/gamedata/scripts/arti_jamming.script

1025 lines
28 KiB
Plaintext

local ini_parts = itms_manager.ini_parts
parts_list = a_wpo_parts.parts_list
get_config = a_arti_jamming_mcm.get_config
gc = game.translate_string
math_random = math.random
math_floor = math.floor
local jammin = sound_object("wpo\\jammin")
-- Enum for different types of jams
JamType = {
OK = 0,
Misfire = 1, -- No discharge
FailureToEject = 2, -- Discharge
DoubleFeed = 3, -- Discharge, Need to remove magazine
Classic = 4, -- Just reload
}
-- associate jammed guns to type of jam
local jammed_guns = {}
--[[
properties:
id: id of current weapon, if applicable
has_parts: if weapon has parts or not, determines if rest is filled in
parts: map of part_name -> condition, same as item_parts, minus barrel
part_names: list of part names, these are the mechanism parts only. we will take the other parts as needed
barrel_name: name of barrel in weapon, for barrel specific calculation
barrel_con: cond of barrel in weapon
trigger_name: name of trigger
]]
local cgd = {}
local ini_weapon = ini_file("wpo\\weapons\\importer.ltx")
local guarantee_not_jam = 0
-- use to block inputs while animations / unjam logic fires
local unjam_in_progress = false
-------------------
-- SECTION utils --
-------------------
function print_dbg(txt, ...)
local enable_debug = get_config("debug") or false
if enable_debug then
printf("arti_jamming | %s | " .. txt, time_global(), ...)
end
end
function send_msg(msg)
if get_config("verbosity") then
actor_menu.set_msg(1, gc(msg),3)
end
end
-- export functions
function get_jam_status(id)
return jammed_guns[id]
end
function set_jam_status(id, status)
print_dbg("Id %s set to %s", id, status)
jammed_guns[id] = status
end
function get_jammed(id)
return jammed_guns[id] and jammed_guns[id] > 0
end
local can_swear = true
--gun jam play swearing sound
function jam_swearing()
local timeout = get_config("profanity_timeout")
if timeout and not can_swear then return end
if get_config("profanity") and math_random(1,100) > 50 then
local lang = get_config("profanity_language") or "rus"
utils_obj.play_sound("cutscenes\\"..lang.."\\gun_jam_"..math_random(1,7), 1 )
if timeout then
can_swear = false
CreateTimeEvent("wpo", "reset_swear", 2, reset_swear)
end
end
end
function reset_swear()
can_swear = true
return true
end
function start_jammin(chance)
local fun_level = get_config("fun")
fun_level = tonumber(fun_level)
print_dbg("fun level: %s", fun_level)
if fun_level == 0 then
return
end
if fun_level == 2 then
chance = chance * 2
end
if not jammin:playing() and math.random(100) <= chance then
jammin.volume = 1
jammin:play(db.actor, 0, sound_object.s2d)
end
end
function stop_jammin()
if jammin:playing() then
jammin:stop()
end
end
function is_part(part)
return parts_list[part]
end
function is_barrel(part)
return is_part(part) and string.find(part, "barrel")
end
function is_trigger(part)
return is_part(part) and string.find(part, "trigger")
end
local function parent_section(sec)
return SYS_GetParam(0, sec,"parent_section") or sec
end
function get_parts_list(wpn)
if wpn and IsWeapon(wpn) and (not IsMelee(wpn)) and not IsItem("fake_ammo_wpn",sec) then
local sec = parent_section(wpn:section())
return ini_parts:r_string_ex("con_parts_list", sec)
else
return nil
end
end
-- true if item is a firearm with parts
function has_parts(wpn)
return get_parts_list(wpn) ~= nil
end
function sec_has_parts(sec)
sec = parent_section(sec)
if sec then
return string.find(sec, "wpn_") and ini_parts:r_string_ex("con_parts_list", sec)
else return false
end
end
function missing_parts(wpn, check_broken)
local comp = check_broken and -1 or 0
if wpn:id() == cgd.id then
if not cgd.has_parts then return false end
if cgd.barrel_con < comp then return true end
for k,v in pairs(cgd.parts) do
if v < comp then return true end
end
else
local parts = item_parts.get_parts_con(wpn, nil, true)
if not parts or is_empty(parts) then return false end
for k,v in pairs(parts) do
if is_part(k) and v < comp then return true end
end
end
return false
end
function play_anim(weapon, anim, sound)
local length = weapon:play_hud_motion(anim, true, 0, 1, 0)
print_dbg("playing animation %s for %s time", anim, length)
if sound then
utils_obj.play_sound(sound)
end
return length
end
function persist_current_weapon()
if cgd.id and cgd.has_parts then
local saved_parts = se_load_var(cgd.id, nil, "parts")
local copy_parts = {}
copy_table(saved_parts, copy_parts)
for k,v in pairs(cgd.parts) do
copy_parts[k] = v
end
copy_parts[cgd.barrel_name] = cgd.barrel_con
item_parts.set_parts_con(cgd.id, copy_parts)
print_dbg("Persisted parts for weapon %s", cgd.id)
end
end
-- current weapon access/update
function update_part(part, cond)
if cgd.barrel_name == part then
cgd.barrel_con = cond
elseif cgd.parts[part] then
cgd.parts[part] = cond
end
persist_current_weapon()
end
function remove_part(part)
update_part(part, -1)
end
function get_parts()
local parts = {}
copy_table(cgd.parts, parts)
parts[cgd.barrel_name] = cgd.barrel_con
return parts
end
function current_id()
return cgd.id
end
function reset_cgd()
clear_cgd()
update_current()
end
----------------------------
-- SECTION main functions --
----------------------------
function actor_on_first_update()
update_current()
end
function clear_cgd()
empty_table(cgd.parts)
empty_table(cgd.data)
empty_table(cgd)
cgd.id = 0
cgd.has_parts = false
end
-- update current gun to be gun in hand
function update_current()
local wpn = db.actor:active_item()
if not wpn then return end
local id = wpn:id()
if id == cgd.id then return end
local sec = wpn:section()
if not has_parts(wpn) then
clear_cgd()
cgd.id = id
print_dbg("Current wpn (no parts) changed to %s (%s)", cgd.id, sec)
else
cgd.id = id
cgd.has_parts = true
print_dbg("Current wpn changed to %s (%s)", cgd.id, sec)
cgd.current_wpn_shot_dec = wpn and SYS_GetParam(2, sec,"condition_shot_dec") or 0.0005
-- consider upgrades
local upgrades = utils_item.get_upgrades_installed(obj)
for _, upgrade in pairs(upgrades) do
local section = ini_sys:r_string_ex(upgrade, "section")
local shot_dec = ini_sys:r_string_ex(section,"condition_shot_dec")
if shot_dec then
local op = string.sub(shot_dec, 1, 1)
local val = tonumber(string.sub(shot_dec, 2))
cgd.current_wpn_shot_dec = op == "+" and cgd.current_wpn_shot_dec + val or cgd.current_wpn_shot_dec - val
end
end
-- ingest parts
cgd.parts = {}
local saved_parts = item_parts.get_parts_con(wpn, nil, true)
-- fix mistakes - if weapon should have parts but were deleted, delete and restore
for k, v in pairs(saved_parts) do
print_dbg("Part: %s, condition of part: %s", k, v)
if is_trigger(k) then
cgd.trigger_name = k
cgd.parts[k] = v
elseif is_barrel(k) then
cgd.barrel_name = k
cgd.barrel_con = v
elseif is_part(k) then
cgd.parts[k] = v
end
end
-- manage family data
cgd.data = {}
local parent = parent_section(sec)
if not ini_weapon:section_exist(parent) then parent = "base" end
cgd.data.jam_chance = larp.str_explode_num(ini_weapon:r_string_ex(parent, "jam_chance") or "0.001, 0.03, 0.09, 0.27, 0.81", ",")
cgd.data.max_jam_chance = ini_weapon:r_float_ex(parent, "max_jam_chance") or 1
cgd.data.misfire_chance = larp.str_explode_num(ini_weapon:r_string_ex(parent, "misfire_chance"), ",")
cgd.data.superjam_mult = ini_weapon:r_float_ex(parent, "superjam_mult") or 1
cgd.data.damage_chance_mult = ini_weapon:r_float_ex(parent, "damage_chance_mult") or 1
cgd.data.barrel_wear = ini_weapon:r_float_ex(parent, "barrel_wear") or 1
cgd.ammo_data = utils_item.get_ammo(nil, wpn:id())
end
end
function get_ammo_impair(wpn)
if wpn:id() ~= cgd.id or not get_config("oldammo") then return 1 end
local ammo = cgd.ammo_data[wpn:get_ammo_type()+1]
return ammo and SYS_GetParam(2, ammo, "impair") or 1
end
-- returns true if using ammo with impair > 1
function using_old_ammo(wpn)
return get_ammo_impair(wpn) > 1
end
-- individually calculate jam based on weapon parts, and jam the weapon if applicable
-- Average jam chance based on damage level of each component.
local function calculate_jam(wpn)
if guarantee_not_jam > 0 then
return
end
jam_chance = calculate_jam_chance(wpn)
print_dbg("Final jam chance: %s", jam_chance)
if (math_random() < jam_chance) then
if wpn:get_ammo_in_magazine() > 0 and get_config("superjam") and (math_random() < (cgd.data.superjam_mult * jam_chance/2)) then
set_jam_status(wpn:id(), JamType.DoubleFeed)
print_dbg("Severe jam")
else
set_jam_status(wpn:id(), JamType.FailureToEject)
print_dbg("Normal jam")
end
start_jammin(2)
CreateTimeEvent("arti_jamming", "jamsound", 0.1,
function(sec, key)
play_sound(nil, 1, sec, key)
return true
end
, wpn:section(), "jam_sound")
send_msg("ui_st_jam")
end
end
-- assumes weapon being used is current
function calculate_jam_chance(wpn, extra)
-- compute base chance from quality of weapon parts
local count = 0
local mechanism_cond = #cgd.parts
for k, v in pairs(cgd.parts) do -- skip barrel
local severity = 5 - clamp(math_floor(v / 20), 0, 4)
mechanism_cond = mechanism_cond + cgd.data.jam_chance[severity]
count = count + 1
end
jam_chance = mechanism_cond/count
if not extra then return jam_chance end
-- apply ammo, mcm and condition enhancements
local ammo_impair = get_ammo_impair(wpn)
if ammo_impair > 0 then
if jam_chance == 0 then jam_chance = clamp(0.1 * (ammo_impair - 1), 0, 0.05)
else
jam_chance = jam_chance * ammo_impair
end
end
-- enhancing jam chance
jam_chance = jam_chance * get_config("jamchance")
-- condition can increase jam chance by up to 3x
local cond = wpn:condition()
local ratio = cond < 0.9 and 1 + (0.9-cond)/0.45 or 1
jam_chance = jam_chance * ratio
jam_chance = clamp(jam_chance, 0, cgd.data.max_jam_chance)
return jam_chance
end
-- Randomly damage 1 part of the weapon by DAMAGE, skipping barrels
-- wear reduces damage and happens more frequently
local function damage_part(wpn, wear)
local to_damage = random_key_table(cgd.parts)
local part_con = cgd.parts[to_damage]
local amt = wear and 1 or math_random(5, 12)
cgd.parts[to_damage] = clamp(part_con - amt, 0, 99)
print_dbg("%s damaged %s amt to %s condition", to_damage, amt, cgd.parts[to_damage])
if get_config("verbosity") then
news_manager.send_tip(db.actor, gc("ui_st_parts_damage"), nil, "swiss_knife", 6000)
end
if not wear then
CreateTimeEvent("arti_jamming", "damsound", 0.1,
function()
play_sound(nil, 0.5, wpn:section(), "damage_sound")
return true
end)
end
end
-- Calculate chance to damage based on weapon condition
local function calculate_damage(wpn)
local cond = wpn and wpn:condition() or 1
local damaged = false
local roll = math_random()
local damage_chance = calculate_damage_chance(wpn)
if (roll < damage_chance) then
damage_part(wpn)
damaged = true
else
-- wear is mostly an omnipresent threat
wear_chance = clamp(damage_chance * 1.5, 0, 1)
if math_random() < wear_chance then
damage_part(wpn, true)
damaged = true
end
end
-- calculate random condition damage
if wpn:condition() > 0.2 then
local chance = 2 * calculate_jam_chance(wpn)
if math_random() < chance then
local degrade = math_random(3)/100
wpn:set_condition(wpn:condition() - degrade, 0, 1)
damaged = true
end
end
local barrel_wear = calculate_barrel_wear(wpn, old_ammo)
print_dbg("Calculating damage: Old ammo %s, barrel wear %s, Final: %s", old_ammo, barrel_wear, damage_chance)
if (roll < barrel_wear) then
print_dbg("Damaged barrel")
cgd.barrel_con = cgd.barrel_con > 0 and cgd.barrel_con - 1 or 0
damaged = true
end
if damaged then
persist_current_weapon()
end
end
-- damage chance is 0-1
function calculate_damage_chance(wpn)
local cond = wpn and wpn:condition() or 1
local threshold = (get_config("threshold") or 85)/100
local damage_chance = 0
old_ammo = get_ammo_impair(wpn)
if (cond < threshold) then
-- 200 x (threshold - condition) * per shot decrease, should result in pretty small chances
damage_chance = (threshold - cond) * cgd.current_wpn_shot_dec * 200 * (cgd.data.damage_chance_mult or 1)
if old_ammo > 1 then
damage_chance = damage_chance * old_ammo
end
else
if old_ammo > 1 then damage_chance = clamp(0.1 * (old_ammo - 1), 0, 0.05) end
end
return damage_chance
end
function calculate_barrel_wear(wpn)
old_ammo = using_old_ammo(wpn) and 2 or 1
return old_ammo * cgd.data.barrel_wear
end
-- calculate misfire change of held weapon
function calculate_misfire()
local trigger_con = cgd.parts[cgd.trigger_name] or 99
local severity = 5 - clamp(math_floor(trigger_con / 20), 0, 4)
local misfire_chance = cgd.data.misfire_chance[severity]
misfire_chance = misfire_chance * get_config("jamchance")
return misfire_chance
end
local last_wpn_snd_empty = {nil, "$no_sound", 1}
local tg_snd_empty = 0
local delay_snd_empty = 300 -- ms
-- before fire - misfire, block shot if jammed with same clicky sound for zeroed weapons
function actor_on_weapon_before_fire(flags)
local wpn = db.actor:active_item()
local id = wpn and wpn:id() or 0
if wpn and id ~= cgd.id then
-- populate current gun
update_current()
end
if not wpn or id ~= cgd.id or wpn:get_ammo_in_magazine() == 0 or not cgd.has_parts or wpn:weapon_in_grenade_mode() then return end
-- if missing parts do not roll for misfire
if missing_parts(wpn, true) then
send_msg("ui_st_missing")
flags.ret_value = false
return
end
-- if guarantee not jam, don't even calculate this
if guarantee_not_jam > 0 then
return
end
-- misfire the weapon
local misfire_chance = calculate_misfire()
print_dbg("Misfire chance: "..misfire_chance)
-- soft misfire
-- if math_random() < clamp(misfire_chance * 3, 0, 1) then
-- flags.ret_value = false
-- return
-- end
-- FTE/Double feed
if not get_jammed(id) then
calculate_jam(wpn)
if get_jammed(id) then
return
end
end
-- hard misfire
if not get_jammed(id) and math_random() < misfire_chance then
set_jam_status(id, JamType.Misfire)
end
local str = ""
-- check for malfunction
if get_jammed(id) then
local sec = wpn:section()
-- Cache
if (sec ~= last_wpn_snd_empty[1]) then
-- Reset
last_wpn_snd_empty[1] = sec
last_wpn_snd_empty[2] = "$no_sound"
last_wpn_snd_empty[3] = 1
-- Get empty sound
local snd = ini_sys:r_string_ex(sec,"snd_empty")
if snd and (snd ~= "") then
snd = str_explode(snd,",")
last_wpn_snd_empty[2] = snd[1]
last_wpn_snd_empty[3] = snd[2] or 1
end
end
-- Play empty clip sound
local tg = time_global()
if (last_wpn_snd_empty[2] ~= "$no_sound") and (tg > tg_snd_empty) then
utils_obj.play_sound( last_wpn_snd_empty[2], last_wpn_snd_empty[3] or 1 )
tg_snd_empty = tg + delay_snd_empty
end
--ADDED jam_swearing()
jam_swearing()
start_jammin(2)
if (get_jam_status(id) == JamType.DoubleFeed) then
str = "ui_st_superjam"
else
str = "ui_st_jam"
end
-- Don't shoot
flags.ret_value = false
send_msg(str)
end
end
-- after fire - chance of FTE, double feed
-- if shoot success, calculate damage
function actor_on_weapon_fired(obj, wpn, ammo_elapsed, grenade_elapsed, ammo_type, grenade_type)
if (obj:id() ~= AC_ID) then
return
end
local id = wpn:id()
if id ~= cgd.id or not cgd.has_parts or wpn:get_ammo_in_magazine() == 0 or missing_parts(wpn) or wpn:weapon_in_grenade_mode() then
return
end
-- calc and apply jam
wpn = utils_item.item_is_fa(wpn) and wpn
if not wpn then return end
if get_jammed(id) then
level.release_action(bind_to_dik(key_bindings.kWPN_FIRE))
end
if not cgd.parts or is_empty(cgd.parts) then return
elseif (not get_jammed(id)
or get_jam_status(id) == JamType.FailureToEject
or get_jam_status(id) == JamType.DoubleFeed) then
calculate_damage(wpn)
end
if guarantee_not_jam > 0 then
guarantee_not_jam = guarantee_not_jam - 1
end
-- calc and apply extra degradation in rain
local degradation_factor = 1
if level.rain_factor() > 0.3 and not GetEvent("current_safe_cover") then
degradation_factor = degradation_factor * (get_config("degradation") or 1.5)
end
if degradation_factor > 1 then
degradation_factor = degradation_factor - 1
local degradation = degradation_factor * cgd.current_wpn_shot_dec
print_dbg("Degrading by additional %s", degradation)
wpn:set_condition(clamp(wpn:condition() - degradation, 0, 0.999))
end
end
function check_unjam(wpn)
return get_jammed(wpn:id())
end
function string_unjam()
return gc("st_unjam")
end
function get_sound(sec, key)
print_dbg("Get sound %s %s", sec, key)
key = key or "unjam_sound"
sec = sec and parent_section(sec) or "base"
sec = ini_weapon:section_exist(sec) and sec or "base"
return "wpo\\"..ini_weapon:r_string_ex(sec, key)
end
function play_sound(snd, vol, sec, key)
print_dbg("Play sound %s %s %s %s", snd, vol, sec, key)
if not snd then
snd = get_sound(sec, key)
end
print_dbg("Playing %s", snd)
utils_obj.play_sound(snd, vol)
end
function toggle_slot(slot)
db.actor:activate_slot(slot)
return true
end
-- do an unjam, optionally playing sounds and such
function apply_unjam(id, sound, message)
if not unjam_in_progress then
print_dbg("Unjam interrupted for %s", id)
return true
end
unjam_in_progress = false
if sound and sound ~= "" then
play_sound(sound)
end
if message then
send_msg(message)
end
if id and id ~= 0 then
set_jam_status(id, nil)
guarantee_not_jam = 1
end
return true
end
function check_simple(weapon)
local str = gc("ui_st_functional")
send_msg(str)
guarantee_not_jam = 1
end
function check_anim(weapon)
check_simple(weapon)
local sec = weapon:section()
local idle_section = utils_item.addon_attached(weapon ,"gl") and (weapon:weapon_in_grenade_mode() and "anm_bore_g" or "anm_bore_w_gl") or "anm_bore"
local hud = ini_sys:r_string_ex(sec, "hud")
print_dbg("searching for "..idle_section.. " for weapon section "..hud)
local bored_anim = hud and ini_sys:r_string_ex(hud, idle_section)
if bored_anim then
weapon:switch_state(4)
guarantee_not_jam = 1
end
end
-- put down and bring back up
function unjam_simple(weapon)
stop_jammin()
local str = gc("ui_st_unjam")
local id = weapon:id()
local sec = parent_section(weapon:section())
local sound = get_sound(sec)
local active_slot = db.actor:active_slot()
toggle_slot(0)
unjam_in_progress = true
CreateTimeEvent("arti_jamming", "unjam"..id, 0.1,
unjam_simple_timed,
active_slot, id, sound, str)
end
function unjam_simple_timed(slot, id, sound, message)
if not unjam_in_progress then
print_dbg("unjam_replace_simple interrupted")
toggle_slot(slot)
return true
end
local current_weapon = db.actor:active_item()
if current_weapon then return false end
toggle_slot(slot)
-- premature interrupt
apply_unjam(id, sound, message)
return true
end
-- unjam held weapon with animation
function unjam_anim(weapon)
-- don't unjam if weapon is still in Fire state
if weapon:get_state() == 5 then
return
end
stop_jammin()
local str = gc("ui_st_unjam")
local id = weapon:id()
local sec = weapon:section()
-- blindside compat, check for unjaminations and cache anim name and sound
local unjam_anims = get_unjam_animation(weapon)
if unjam_anims ~= nil then
unjam_in_progress = true
local to_search = utils_item.addon_attached(weapon ,"gl") and "anm_reload_misfire_w_gl" or "anm_reload_misfire"
local unjam_sound = ini_sys:r_string_ex(sec, "snd_reload_misfire") or ini_sys:r_string_ex(sec, "snd_reload_1") or "$no_sound"
-- shorten time so that jam is cleared before the next animation plays
local length = play_anim(weapon, to_search, unjam_sound) - 10
CreateTimeEvent("arti_jamming", "restore", length/1000, apply_unjam, id)
end
end
function unjam_super_simple(weapon)
if weapon:get_ammo_in_magazine() > 1 then
start_jammin(50)
jam_swearing()
send_msg(gc("ui_st_unjam_fail"))
weapon:switch_state(2)
else
unjam_simple(weapon)
end
end
function unjam_super_anim(weapon)
if weapon:get_ammo_in_magazine() > 1 then
start_jammin(50)
jam_swearing()
send_msg(gc("ui_st_unjam_fail"))
weapon:switch_state(2)
else
unjam_anim(weapon)
end
end
function determine_slot(weapon)
for i=1,5 do
local item = db.actor:item_in_slot(i)
local item_id = item and item:id() or 0
if weapon:id() == item_id then return i end
end
end
-- clear the entry, no qs asked
function inv_unjam(weapon)
if get_jam_status(weapon:id()) > 1 then return end
unjam_in_progress = true
apply_unjam(weapon:id(), get_sound(weapon:section()))
end
function unjam_and_clone(weapon, slot, sound)
if not unjam_in_progress then
print_dbg("unjam_replace_simple interrupted")
toggle_slot(slot)
return true
end
local current_weapon = db.actor:active_item()
if current_weapon then return false end
apply_unjam(weapon:id(), sound)
local old_weapon = alife_object(weapon:id())
local new_weapon = alife_clone_weapon(old_weapon)
print_dbg("cloned weapon, doing restoration")
CreateTimeEvent("arti_jamming", "move_to_slot", 0.1,
function(weapon, slot)
print_dbg("unjam_replace_simple - restoring weapon to slot %s", slot)
db.actor:move_to_slot(level.object_by_id(weapon), slot)
toggle_slot(slot)
return true
end,
new_weapon.id, slot)
return true
end
-- hide, clone, move
function unjam_replace_simple(weapon)
if not weapon then return end
local slot = determine_slot(weapon)
local sound = get_sound(weapon:section())
print_dbg("slot determined is %s", slot)
unjam_in_progress = true
local active_slot = db.actor:active_slot()
toggle_slot(0)
print_dbg("replacing weapon %s", weapon:id())
CreateTimeEvent("arti_jamming", "move "..weapon:id(), 0.1, unjam_and_clone, weapon, slot, sound)
print_dbg("unjam replace returning")
return true
end
-- just change the state to reloading and check when reloading is done
function unjam_replace_anim(weapon)
weapon:switch_state(7)
unjam_in_progress = true
apply_unjam(weapon:id())
end
function inv_unjam_replace(weapon)
unjam_in_progress = true
apply_unjam(weapon:id(), get_sound(weapon:section()))
local slot = determine_slot(weapon)
local old_weapon = alife_object(weapon:id())
local new_weapon = alife_clone_weapon(old_weapon)
if slot then
CreateTimeEvent("arti_jamming", "move_to_slot", 0.1,
function(weapon, slot)
print_dbg("unjam_replace_simple - restoring weapon to slot %s", slot)
db.actor:move_to_slot(level.object_by_id(weapon), slot)
toggle_slot(slot)
return true
end,
new_weapon.id, slot)
end
end
-- conditions:
-- unjam type? ok (0), misfire/feed (just 1), failure to eject(2), doublefeed(3), classic (4)
-- currently held?
-- has animation?
-- these functions will be completely responsible for what happens
local unjam_router = {
-- held or not
held = {
[JamType.OK] = {
anim = check_anim,
no_anim = check_simple
},
[JamType.Misfire] = {
anim = unjam_anim,
no_anim = unjam_simple
},
[JamType.FailureToEject] = {
anim = unjam_anim,
no_anim = unjam_simple
},
[JamType.DoubleFeed] = {
anim = unjam_super_anim,
no_anim = unjam_super_simple
},
[JamType.Classic] = {
anim = unjam_replace_anim,
no_anim = unjam_replace_simple
}
},
inventory = {
[JamType.Misfire] = {
anim = inv_unjam,
no_anim = inv_unjam
},
[JamType.FailureToEject] = {
anim = inv_unjam,
no_anim = inv_unjam
},
[JamType.DoubleFeed] = {
anim = inv_unjam,
no_anim = inv_unjam
},
[JamType.Classic] = {
anim = inv_unjam_replace,
no_anim = inv_unjam_replace
}
}
}
-- Unjam works by clearing weapon ID from the jam table, if conditions are met.
function unjam(wpn)
local weapon = wpn or db.actor:active_item()
if not weapon then return end
if unjam_in_progress then
print_dbg("weapon already being unjammed")
return
end
local id = weapon:id()
local active_id = db.actor:active_item() and db.actor:active_item():id() or 0
local sec = weapon:section()
if not weapon then
return
end
if missing_parts(weapon) then
send_msg("ui_st_missing")
return
end
local is_current = id == active_id and "held" or "inventory"
local jam_level = get_jam_status(id) or 0
local has_anim = get_unjam_animation(weapon) and "anim" or "no_anim"
print_dbg("unjam current: %s jam level: %s has_anims: %s. has function? %s", is_current, jam_level, has_anim, unjam_router[is_current][jam_level][has_anim] ~= nil)
if unjam_router[is_current][jam_level][has_anim] then
unjam_router[is_current][jam_level][has_anim](weapon)
end
end
function get_unjam_animation(weapon)
local sec = weapon:section()
local to_search = utils_item.addon_attached(weapon ,"gl") and "anm_reload_misfire_w_gl" or "anm_reload_misfire"
local hud = ini_sys:r_string_ex(sec, "hud")
print_dbg("searching for "..to_search.. " for weapon section "..hud)
local unjam_anims = hud and ini_sys:r_string_ex(hud,to_search)
return unjam_anims
end
-- SECTION callbacks --
local disallowed = {
[key_bindings.kINVENTORY] = true,
[key_bindings.kWPN_RELOAD] = true,
[key_bindings.kNIGHT_VISION] = true,
[key_bindings.kWPN_NEXT] = true,
[key_bindings.kWPN_1] = true,
[key_bindings.kWPN_2] = true,
[key_bindings.kWPN_3] = true,
[key_bindings.kWPN_4] = true,
[key_bindings.kWPN_5] = true,
[key_bindings.kWPN_6] = true,
}
function on_key_press(key)
-- certain actions interrupt any unjams in progress
local bind = dik_to_bind(key)
if unjam_in_progress and disallowed[bind] then
print_dbg("unjam interrupted by action")
unjam_in_progress = false
end
end
function actor_on_weapon_jammed(actor)
local wpn = db.actor:active_item()
if not get_jammed(wpn:id()) then
if not pcall(function(wpn)
set_jam_status(wpn:id(), JamType.FailureToEject)
end, wpn) then
set_jam_status(wpn:id(), JamType.Classic)
end
end
wpn:cast_Weapon():SetMisfire(false)
jam_swearing()
play_sound(nil, 2, wpn:section(), "jam_sound")
end
function actor_on_weapon_reload(wpn)
local id = wpn:id()
local jam_lv = get_jam_status(id)
if jam_lv then
if magazines and magazines.do_interrupt_reload then magazines.do_interrupt_reload() end
-- drop mag if simplejam enabled and superjammed
if get_config("simplejam") and jam_lv == JamType.DoubleFeed then
-- on simplejam, unload the magazine
mag_support.eject_mag(wpn)
end
CreateTimeEvent("arti_jamming", "cancel_reload", 0.1,
function(wpn)
wpn:switch_state(2)
return true
end
, wpn)
end
end
-- Reduces player outgoing damage based on barrel quality, up to 50% --
local function reduce_damage(npc, shit, bone_id, flags)
-- skip if it wasnt a hit by the player
if not (shit.draftsman and shit.draftsman:id() == 0) then
return
end
-- skip if player has no weapon out, or weapon has no parts
local weapon = db.actor:active_item()
if not weapon or barrel_con == nil then
return
end
if barrel_con > 80 then
return
else
local ratio = (80 - barrel_con)/80
shit.power = shit.power * (1 - (0.5 * ratio))
end
end
function npc_on_before_hit(npc,shit,bone_id, flags)
reduce_damage(npc, shit, bone_id, flags)
end
function monster_on_before_hit(monster,shit,bone_id, flags)
reduce_damage(monster, shit, bone_id, flags)
end
-- Clear jam mid animation with motion marks
function actor_on_hud_animation_mark(state, mark)
if mark == "clear_jam" then
ResetTimeEvent("arti_jamming", "restore", 0)
end
end
function on_game_start()
-- add custom functors
custom_functor_autoinject.add_functor("arti_unjam", check_unjam, string_unjam, nil, unjam, false)
RegisterScriptCallback("on_key_press",on_key_press)
RegisterScriptCallback("actor_on_weapon_jammed",actor_on_weapon_jammed)
RegisterScriptCallback("actor_on_weapon_before_fire",actor_on_weapon_before_fire)
RegisterScriptCallback("actor_on_weapon_fired",actor_on_weapon_fired)
RegisterScriptCallback("actor_on_weapon_reload",actor_on_weapon_reload)
RegisterScriptCallback("actor_on_first_update",actor_on_first_update)
RegisterScriptCallback("npc_on_before_hit",npc_on_before_hit)
RegisterScriptCallback("monster_on_before_hit",monster_on_before_hit)
RegisterScriptCallback("actor_on_hud_animation_mark",actor_on_hud_animation_mark)
end