Divergent/mods/OneKey Weapon Controls/gamedata/scripts/magazines.script

1065 lines
33 KiB
Plaintext
Raw Normal View History

2024-03-17 20:18:03 -04:00
----------------------------------------------------------------
-- Refactored magazines by Arti and Raven. Most of this code is cribbed from wuut_mags original, but cleaned up significantly.
-- Modified by TedHo, reload key presses are now handled by onekey_reload_mcm. Also performs an ammo-check on reload.
----------------------------------------------------------------
--Function alises
gc = game.translate_string
get_mag_data = magazine_binder.get_data
set_mag_data = magazine_binder.set_data
is_supported_weapon = magazine_binder.is_supported_weapon
get_magazine_caliber = magazine_binder.get_magazine_caliber
weapon_default_magazine = magazine_binder.weapon_default_magazine
is_compatible = magazine_binder.is_compatible
isMagazine = magazine_binder.is_magazine
is_magazine = magazine_binder.is_magazine
get_mag_loaded = magazine_binder.get_mag_loaded
is_carried_mag = magazine_binder.is_carried_mag
toggle_carried_mag = magazine_binder.toggle_carried_mag
get_carried_mags = magazine_binder.get_carried_mags
room_in_pouch = magazine_binder.room_in_pouch
get_retool_section = magazine_binder.get_retool_section
get_weapon_base_type = magazine_binder.get_weapon_base_type
get_magazine_base_type = magazine_binder.get_magazine_base_type
get_mags_for_basetype = magazine_binder.get_mags_for_basetype
dump_data = magazine_binder.dump_data
get_mags_by_ammo_type = magazine_binder.get_mags_by_ammo_type
get_config = magazines_mcm.get_config
local dbg_log
function print_dbg( text , ...)
if (magazines_mcm.get_config("debug") or false) then
dbg_log = dbg_log or mcm_log and mcm_log.new("DBG")
if dbg_log then
dbg_log.enabled = true
dbg_log:log( text , ...)
else
printf( "RAX: | %s | "..text ,time_global(), ...)
end
end
return nil
end
local err_log
function print_err( text , ...)
if (magazines_mcm.get_config("debug") or false) then
err_log = err_log or mcm_log and mcm_log.new("ERR")
if err_log then
err_log.enabled = true
err_log:log( text , ...)
end
printe( "ERR: | %s | "..text ,time_global(), ...)
end
return nil
end
-- actor action state
local action_state = 0
-- states (idle, mag to weapon reload in progress, ammo to mag load in progress)
ACTION_NONE = 0
ACTION_RELOAD = 1
ACTION_LOAD_MAG = 2
local jammed_guns = {}
local load_timeout = {}
-- due to changing how mag loading works (1 big event vs. several events) we need this here to persist which box we are taking ammo from
local ext_ammo_box = nil
-- flag to force reload during the animation instead of afterwards
local force_reload = false
------------------------
-- SECTION debug info --
------------------------
local function parent_is_box(obj)
local parent = obj:parent()
if parent:id() == 0 then return false end
local se_parent = alife_object(parent:id())
if se_parent:clsid() == clsid.inventory_box_s then
return true
end
return false
end
function action_in_progress()
return action_state ~= ACTION_NONE
end
function action_start_reload()
action_state = ACTION_RELOAD
end
function action_start_magload()
action_state = ACTION_LOAD_MAG
end
function do_interrupt_reload()
if action_state == ACTION_RELOAD then
action_state = ACTION_NONE
end
end
function do_interrupt_magload()
if action_state == ACTION_LOAD_MAG then
action_state = ACTION_NONE
end
end
function do_interrupt()
action_state = ACTION_NONE
end
local time_event_cache = {}
function create_time_event(ev_id,act_id,timer,f,...)
disable_info("sleep_active")
time_event_cache[ev_id .. "|" .. act_id] = true
CreateTimeEvent(ev_id,act_id,timer,f,...)
end
--------------------------------------
-- SECTION mag un/loading functions --
--------------------------------------
-- Get the time delay between loading/unloading rounds from/to magazine
--
-- **mag_section** *(string)* = section of the mag to get **load_delay** param if **ignore_loaddelay** is false
--
-- **is_unload** *(bool)* = should we get mag unload time
cache_mag_time = {}
function get_mag_time(mag_section,is_unload)
is_unload = is_unload or false
local lookup_key = mag_section .. (is_unload and "_unload" or "_load")
if not cache_mag_time[lookup_key] then
local default_delay = SYS_GetParam(2, mag_section, "load_delay") or 0.4
cache_mag_time[lookup_key] = default_delay * (is_unload and get_config('mag_unloadtime_factor') or get_config('mag_loadtime_factor'))
end
return cache_mag_time[lookup_key]
end
-- check if load/unload delay has been altered and wipe cache
function on_option_change()
empty_table(cache_mag_time)
end
function timed_refresh()
ResetTimeEvent("mag_redux", "timed_refresh", 0.5) -- if called while event is still active extend the time to allow the new change to take effect before refreshing.
create_time_event("mag_redux", "timed_refresh", 0.5, function() --needs a bit of delay to work consistently.
inventory_refresh(2)
return true
end)
end
function inventory_refresh(mode) --safer way to refresh inventory, mode == 1 refreshes sorting.
if not mode then
mode = get_config("sort_inv") and 1 or 2
end
local inventory = GetActorMenu()
if actor_menu.get_last_mode() == 0 then return end --don't refresh if UI not open.
if mode == 1 then
local sort = nil
for i=1,#inventory.sort_btn do
if inventory.sort_btn[i]:GetCheck() then
sort = i
end
end
inventory:On_Sort(sort or 1,false)
else
inventory:UpdateItems()
end
end
local ammo_maps = {} --cacheing. this function will get called a lot in combat. ammo_maps gets niled in load_weapon should be enough protect against id recycling and weapon rechamper upgrades
function get_ammo_map(id)
if not ammo_maps[id] then
ammo_maps[id] = invert_table(utils_item.get_ammo(nil, id))
end
-- print_dbg("ammo_maps[id][1]:%s ammo_maps[id]:%s", ammo_maps[id] and ammo_maps[id][1], ammo_maps[id])
return ammo_maps[id]
end
-- return the section of the ammo loaded in weapon
function get_sec_chambered(wpn)
local id = wpn:id()
local index = wpn:get_ammo_type()
if wpn:get_ammo_in_magazine() > 0 then
local ammo_map = invert_table(get_ammo_map(id))
return ammo_map[index + 1]
else return nil end
end
function retain_round(weapon)
return get_config("retain_round") and not magazine_binder.is_open_bolt_weapon(weapon)
end
local function se_item_on_unregister(se_obj, typ)
local id = se_obj.id
ammo_maps[id] = nil
end
function on_item_drag_dropped(item, weapon, from_slot, to_slot)
print_dbg("on_item_drag_dropped " .. item:section() .. " on " .. weapon:section() .. " to_slot " .. (to_slot or "nil"))
--Only do one thing at a time
if action_in_progress() then
do_interrupt()
return
end
-- Check capability
if not ((from_slot == EDDListType.iDeadBodyBag and to_slot == EDDListType.iDeadBodyBag) or (from_slot == EDDListType.iActorBag and (to_slot == EDDListType.iActorBag or to_slot == EDDListType.iActorSlot))) then
return
end
local active_id = db.actor:active_item() and db.actor:active_item():id() or 0
local item_id = item:id()
local item_sec = item:section()
local weapon_id = weapon:id()
local weapon_sec = weapon:section()
if(item_id == weapon_id) then
return
end
local is_box_mode = (parent_is_box(item) and from_slot == EDDListType.iDeadBodyBag) and true or false
-- bullet to mag
if(IsAmmo(item) and is_magazine(weapon)) then
-- check compatibility, then fire (heh) the loading loop
local ammos = invert_table(get_magazine_caliber(weapon))
local ammo_sec = item_sec
print_dbg("Checking compat for ammo sec %s", ammo_sec)
if ammos[ammo_sec] ~= nil then
-- start loading loop
local capacity = SYS_GetParam(2, weapon_sec, "max_mag_size") or 20
local behavior = is_box_mode and get_config("loadstash_behavior") or get_config("load_behavior")
if behavior == 1 then
fast_load_magazine(weapon, item)
elseif behavior == 2 then
fast_load_magazine(weapon, item, true)
else
local delay = get_mag_time(weapon_sec, false)
print_dbg("Start load magazine at speed %s", delay)
ext_ammo_box = item
action_start_magload()
RemoveTimeEvent("Mag_redux", "load_mag"..weapon_id)
create_time_event("Mag_redux", "load_mag"..weapon_id, 0.1, load_magazine, weapon, capacity, delay * 1000)
end
end
end
-- mag to weapon
if is_magazine(item) and IsWeapon(weapon) and is_compatible(weapon, item) then
if weapon:weapon_in_grenade_mode() then return end
-- init loading of weapon
print_dbg("Begin loading mag %s into weapon %s", item:section(), weapon:section())
if weapon_id == active_id then
local pre_table = count_ammo(weapon)
weapon:switch_state(7)
action_start_reload()
create_time_event("Mag_redux", "delay_weapon"..weapon:id(), 0.1, delay_load_weapon, weapon, item, pre_table)
else
eject_magazine(weapon)
load_weapon(weapon, item)
end
end
-- bullet to weapon
if (IsAmmo(item) and IsWeapon(weapon)) then
chamber_single_round(item, weapon)
end
end
-- reassign mag data from magazine to weapon
function load_weapon(weapon, magazine)
if ACTION_IN_PROGRESS then return end
if is_magazine(magazine) and IsWeapon(weapon) and is_compatible(weapon, magazine) then
local wep_id = weapon:id()
local existing_mag = get_mag_loaded(wep_id)
if existing_mag then
print_dbg("Cancel loading. Another mag already in wpn: %s", existing_mag.section or "<unknown>")
return
end
local mag_id = magazine:id()
ammo_maps[wep_id] = nil -- nil this here to protect against weapon caliber changes or id recycling.
-- check for empty mag and initialize
local mag_data = get_mag_loaded(mag_id)
if mag_data then
if mag_data.section == "mag_ak_5.56x45_default" then mag_data.section = "mag_ak_5.56x45_modern" end
print_dbg("ammo in weapon existing: %s", weapon:get_ammo_in_magazine())
if retain_round(weapon) and (weapon:get_ammo_in_magazine() > 0 ) then
local first_round = get_sec_chambered(weapon)
print_dbg("extra round of type %s found, adding to mag", first_round)
stack.push(mag_data.loaded, first_round)
end
mag_data.is_weapon = true
set_mag_data(wep_id, mag_data)
set_mag_data(mag_id, nil)
alife_release_id(mag_id)
prep_weapon(weapon, false)
do_interrupt_reload()
end
end
end
local last_ejected
-- 0 = no ammo, 1 = one round, checked, 2 = no round, checked
local save_round = 0
function delay_load_weapon(weapon, magazine, pre_table)
local tg = time_global()
-- extra timer stuff so we don't let the time event linger
local id = weapon:id()
if not load_timeout[id] then
load_timeout[id] = tg
end
local inventory = GetActorMenu()
local active_id = db.actor:active_item() and db.actor:active_item():id() or 0
if action_state ~= ACTION_RELOAD or tg > load_timeout[id] + 50000 or (active_id ~= id) then
load_timeout[id] = nil
do_interrupt()
weapon:switch_state(2)
force_reload = false
last_ejected = nil
save_round = 0
print_dbg("Reload interrupted")
return true
end
local mag_data = get_mag_loaded(id)
if mag_data then
local has_room = room_in_pouch(magazine:id())
local ejected = eject_magazine(weapon)
-- if 3hands or the slots aren't full, store the mag
if has_room or get_config("three_hands") then
last_ejected = ejected
print_dbg("Ejected id %s. Is carried? %s", last_ejected, is_carried_mag(last_ejected))
end
end
if save_round == 0 then
save_round = weapon:get_ammo_in_magazine() > 0 and 1 or 2
end
if weapon:get_state() == 0 or force_reload then
weapon:unload_magazine()
if save_round == 1 then
weapon:set_ammo_elapsed(1)
end
save_round = 0
ACTION_IN_PROGRESS = false
load_weapon(weapon, magazine)
create_time_event("Mag_redux", "refund", 0.1, function()
refund_ammo(weapon, pre_table)
return true
end)
load_timeout[id] = nil
if last_ejected and not is_carried_mag(last_ejected) and retain_magazine(last_ejected) then
print_dbg("Toggling %s", last_ejected)
toggle_carried_mag(last_ejected)
end
force_reload = false
last_ejected = nil
return true
end
return false
end
-- put a single round in the chamber
--only works if they are compatible and weapon is empty
function chamber_single_round(ammo, weapon)
print_dbg("do chamber one round of %s", ammo:section())
if is_supported_weapon(weapon) and retain_round(weapon) and weapon:get_ammo_in_magazine() == 0 then
local ammo_map = get_ammo_map(weapon:id())
local typ = ammo_map[ammo:section()]
if not typ then return end
print_dbg("chamber single round, ammo is %s, type is %s", ammo:section(), typ - 1)
weapon:set_ammo_elapsed(1)
weapon:set_ammo_type(typ - 1)
utils_obj.play_sound("handgun_unjam")
if ammo:ammo_get_count() == 1 then
alife_release_id(ammo:id())
else
ammo:ammo_set_count(ammo:ammo_get_count() - 1)
end
end
end
-- When weapon is reloaded in any way, it will subtract a box from player inventory. We don't want this to happen - loading should only happen from magazines. Record the prior amount of ammo before reload and refund it afterwards.
function count_ammo(weapon)
local ammo_table = {}
local ammo_types = utils_item.get_ammo(weapon:section(), weapon:id())
for i=1,#ammo_types do
ammo_table[ammo_types[i]] = 0
end
db.actor:inventory_for_each(function (item)
local item_section = item:section()
if ammo_table[item_section] then
ammo_table[item_section] = ammo_table[item_section] + item:ammo_get_count()
end
end)
return ammo_table
end
-- Refund ammo.
function refund_ammo(weapon, pre_ammo_table)
print_dbg("Refunding ammo")
if rax_inventory_highlights_mcm then -- yes i am adding a compatablity patch with my own mod.
rax_inventory_highlights_mcm.temp_disable() --don't flag this magazine as new.
end
local post_ammo_table = count_ammo(weapon)
for ammo_type, count in pairs(pre_ammo_table) do
local to_refund = count - post_ammo_table[ammo_type]
if to_refund > 0 then
alife_create_item(ammo_type, db.actor, {ammo = to_refund})
-- create_ammo(ammo_type, db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), AC_ID, to_refund)
end
end
end
function weapon_in_slot(weapon)
local carried_weapons = {}
for i,_ in pairs(SCANNED_SLOTS) do
if db.actor:item_in_slot(i) then
carried_weapons[db.actor:item_in_slot(i):id()] = true
end
end
return carried_weapons[weapon:id()]
end
-- Determine if this magazine should be retained in loadout, based on MCM options.
function retain_magazine(id, ejection)
ejection = ejection or tonumber(get_config("ejection"))
if ejection == 0 then return true end
local mag_data = get_mag_loaded(id)
if mag_data then
local capacity = SYS_GetParam(2, mag_data.section, "max_mag_size") or 20
local ammo_percent = #mag_data.loaded/capacity
if #mag_data.loaded ~= 0 and ammo_percent > (get_config('mag_unequip_trs')/100) then return true end
end
return false
end
local modes = {
["inventory"] = true,
["loot"] = true,
}
local bags = {
["actor_bag"] = true,
["actor_equ"] = true,
["actor_belt"] = true,
["npc_bag"] = true,
}
-- should only fire on weapons!
function check_eject_magazine(weapon, bag, mode)
-- bag check
if not (bags[bag] and modes[mode]) or not is_supported_weapon(weapon) then return end
local mag_data = get_mag_loaded(weapon:id())
if mag_data and not weapon:weapon_in_grenade_mode() then
return gc("st_mag_eject_magazine")
end
end
function eject_magazine(weapon)
if rax_inventory_highlights_mcm then
rax_inventory_highlights_mcm.temp_disable() --don't flag this magazine as new.
end
if weapon:weapon_in_grenade_mode() or not is_supported_weapon(weapon) then return end
local id = weapon:id()
-- additional validation
magazine_binder.validate_wep(id)
local mag_data = get_mag_loaded(id)
if mag_data then
weapon:force_unload_magazine(false)
if retain_round(weapon) and #mag_data.loaded > 0 then
print_dbg("retaining round of type %s in chamber", stack.peek(mag_data.loaded))
stack.pop(mag_data.loaded)
weapon:set_ammo_elapsed(1)
end
se_mag = alife_create_item(mag_data.section, weapon:parent())
if se_mag then
create_time_event("mag_redux", "timer_eject_"..id, 0, timer_eject_magazine, weapon, se_mag.id, mag_data)
else
print_dbg("Could not create magazine %s", mag_data.section)
end
set_mag_data(id, {
section = "no_mag",
loaded = {},
is_weapon = true,
})
dump_data(mag_data)
return se_mag.id
end
end
function timer_eject_magazine(weapon, id, mag_data)
mag_data.is_weapon = false
set_mag_data(id, mag_data)
local ejection_type = tonumber(get_config("ejection"))
print_dbg("ejection type is %s", ejection_type)
local inv = GetActorMenu()
local inv_open = inv and inv:IsShown()
if weapon_in_slot(weapon) then
if retain_magazine(id, ejection_type) or inv_open then
toggle_carried_mag(id)
elseif ejection_type == 2 and not inv_open then
db.actor:drop_item(level.object_by_id(id))
end
end
return true
end
-- locate appropriate mag in vest
function find_magazine(weapon, ammo_type)
if not ammo_type then
ammo_type = utils_item.get_ammo(nil, weapon:id())[weapon:get_ammo_type()+1]
end
local carried_mags = {}
get_carried_mags(carried_mags)
local largest_id = nil
local largest_of_type = nil
for k,v in pairs(carried_mags) do
print_dbg("checking id %s, type is %s, %s %s rounds loaded", k, v.section, #v.loaded, stack.peek(v.loaded))
if is_compatible(weapon, v.section) then
if largest_id == nil or #v.loaded > #carried_mags[largest_id].loaded then
print_dbg("largest so far is %s", k)
largest_id = k
end
if stack.peek(v.loaded) == ammo_type and (largest_of_type == nil or #v.loaded > #carried_mags[largest_of_type].loaded) then
print_dbg("largest of type so far is %s", k)
largest_of_type = k
end
end
end
if largest_of_type then
return level.object_by_id(largest_of_type)
elseif largest_id then
return level.object_by_id(largest_id)
else return nil end
end
--count of magazines by ammo type of first round in vest
function count_magazines(weapon)
local carried_mags = {}
local mag_inv = {}
get_carried_mags(carried_mags)
for k,v in pairs(carried_mags) do
if is_compatible(weapon, v.section) and #v.loaded >0 then
local first_round_sec = stack.peek(v.loaded)
mag_inv[first_round_sec] = (mag_inv[first_round_sec] or 0) + 1
end
end
return mag_inv
end
function vanilla_reload(actor, weapon)
local state = weapon and weapon:get_state()
if not weapon or not is_supported_weapon(weapon) or weapon:weapon_in_grenade_mode() or state == 7 then
return
end
end
function actor_on_weapon_reload(actor, weapon, ammo_total)
local weapon = db.actor:active_item()
local id = weapon:id()
-- cancel reload only if we have a valid mag, do not allow reloads unless weapon is neutral
local state = weapon and weapon:get_state()
if not weapon or not is_supported_weapon(weapon) or weapon:weapon_in_grenade_mode() or state == 7 then
return
end
-- wpo edge case to handle denial of reload on unjam
if arti_jamming and arti_jamming.get_jammed and arti_jamming.get_jammed(id) then
weapon:switch_state(2)
return
end
if is_jammed_weapon(weapon) then
weapon_unjammed(weapon)
local hud_section = SYS_GetParam(0, weapon:section(), "hud")
if SYS_GetParam(0, hud_section, "anm_reload_misfire") == nil then
-- actually do the weapon replace logic here
unjam_weapon(weapon)
end
return
end
-- find appropriate magazine
local mag = find_magazine(weapon)
-- do the reload
if not mag then
print_dbg("No ready mags found, not reloading")
do_interrupt()
create_time_event("Mag_redux", "cancel_reload"..id, 0.1, function (weapon)
weapon:switch_state(2)
return true
end, weapon)
return
end
local pre_table = count_ammo(weapon)
weapon:switch_state(7)
action_start_reload()
-- note to self - setting delay as 0.1 to fix ghost reload
create_time_event("Mag_redux", "delay_weapon"..id, 0.1, delay_load_weapon, weapon, mag, pre_table)
end
function actor_on_update()
FADArun = enhanced_animations and enhanced_animations.used_item and true or false
if action_in_progress() and FADArun then
do_interrupt()
end
end
local timeout = nil
function unjam_weapon(weapon)
timeout = time_global()
local id = weapon:id()
local data = get_mag_data(id)
local slot = db.actor:active_slot()
db.actor:activate_slot(0)
local new_id = id
local replace = false
if not pcall(function(wpn)
wpn:cast_Weapon():SetMisfire(false)
end, weapon) then
-- replace
local old_weapon = alife_object(id)
local new_weapon = alife_clone_weapon(old_weapon)
new_id = new_weapon.id
set_mag_data(id, nil)
replace = true
end
weapon_unjammed(weapon)
print_dbg("New weapon should have %s ammo, time global=%s", data and #data.loaded or "no", timeout)
create_time_event("Mag_redux", "unjam_weapon", 0, unjam_weapon_timer, new_id, data, slot, replace)
end
function unjam_weapon_timer(weapon_id, data, slot, replace)
-- keep rolling while weapon is being drawn, if five seconds total elapse do logic regardless
local current_weapon = db.actor:active_item()
if (current_weapon and current_weapon:id() ~= weapon_id) and time_global() < timeout + 5000 then return false end
print_dbg("Unjamming weapon, timeout=%s", timeout)
utils_obj.play_sound("handgun_unjam")
if replace then
local weapon = level.object_by_id(weapon_id)
print_dbg("ujwt weapon:%s", weapon and weapon:name())
set_mag_data(weapon_id, nil)
set_mag_data(weapon_id, data)
prep_weapon(weapon, false)
if not current_weapon then
db.actor:move_to_slot(weapon, slot)
end
end
db.actor:activate_slot(slot)
return true
end
-----------------------------------------
-- SECTION rounds un/loading functions --
-----------------------------------------
-- iterate inventory and return the next box for this ammo type. return nil if nothing found
function find_next_box(ammo_section, id)
local item_to_get = nil
function grab_inv_items(npc, item)
if IsAmmo(item) then
if ammo_section == item:section() and item:id() ~= id then
item_to_get = item
end
end
end
db.actor:iterate_inventory(grab_inv_items)
return item_to_get
end
local last_time = 0
-- position is ammo type as number
function load_magazine(magazine, capacity, delay)
local tg = time_global()
if tg < last_time + delay then return false end
if IsMoveState("mcAnyMove") and tg < last_time + (3 * delay) then return false end
last_time = tg
print_dbg("Try load magazine, Action state: %s", action_state)
if action_state ~= ACTION_LOAD_MAG or ext_ammo_box == nil then
print_dbg("ending load prematurely. is ammo box nil? %s", ext_ammo_box == nil)
do_interrupt()
inventory_refresh()
last_time = 0
return true
end
local id = magazine:id()
local mag_data = get_mag_data(id)
if (#mag_data.loaded >= capacity) then
print_dbg("ending load with full magazine")
inventory_refresh()
last_time = 0
ext_ammo_box = nil
do_interrupt()
return true
end
local num_bullets = ext_ammo_box:ammo_get_count()
local ammo_section = ext_ammo_box:section()
if num_bullets == 1 then
print_dbg("Searching for next box")
local ext_id = ext_ammo_box:id()
new_ammo_box = find_next_box(ammo_section, ext_id)
alife_release_id(ext_id)
ext_ammo_box = new_ammo_box
else
ext_ammo_box:ammo_set_count(num_bullets - 1)
end
stack.push(mag_data.loaded, ammo_section)
set_mag_data(id, mag_data)
print_dbg("Loaded 1 round of type %s. There are %s rounds loaded total", ammo_section, #mag_data.loaded)
utils_obj.play_sound("magazines\\ammo_load"..math.random(1, 7))
inventory_refresh(2)
return false
end
function unload_magazine(magazine, delay)
local tg = time_global()
if tg < last_time + delay then return false end
if IsMoveState("mcAnyMove") and tg < last_time + (3 * delay) then return false end
last_time = tg
local id = magazine:id()
local mag_data = get_mag_data(id)
-- note to self: create_ammo does not trigger item on move callback, hence cannot unload into corpse or box
local mag_parent = db.actor--magazine:parent() and magazine:parent() or db.actor
if not mag_data or #mag_data.loaded == 0 or action_state ~= ACTION_LOAD_MAG then
print_dbg("ending unload prematurely, state=%s", action_state)
do_interrupt()
inventory_refresh()
-- create_time_event("mag_redux", "box_ammo_aggregation", 0, item_weapon.ammo_aggregation_full, mag_parent:id())
last_time = 0
return true
end
local round = stack.pop(mag_data.loaded)
set_mag_data(id, mag_data)
if round ~= nil then
print_dbg("Unloaded 1 round of type %s. There are %s rounds left.", round, #mag_data.loaded)
create_ammo(round, mag_parent:position(), mag_parent:level_vertex_id(), mag_parent:game_vertex_id(), mag_parent:id(), 1)
utils_obj.play_sound("magazines\\ammo_load"..math.random(1, 7))
else
print_dbg("Mag empty, returning")
-- create_time_event("mag_redux", "box_ammo_aggregation", 0, item_weapon.ammo_aggregation_full, mag_parent:id())
inventory_refresh()
last_time = 0
do_interrupt()
return true
end
end
function fast_load_magazine(magazine, box, load_one)
local mag_data = get_mag_data(magazine:id())
print_dbg("begin fastload")
dump_data(mag_data)
local ammo_sec = box:section()
local parent = box:parent()
local ammo_to_load = SYS_GetParam(2, magazine:section(), "max_mag_size") - #mag_data.loaded
local boxes_to_load = {}
local count_so_far = 0
local function itr_box(temp, item)
if item:section() == ammo_sec and count_so_far < ammo_to_load then
boxes_to_load[item:id()] = true
count_so_far = count_so_far + item:ammo_get_count()
end
end
if load_one then
boxes_to_load[box:id()] = true
elseif IsInvbox(parent) then
parent:iterate_inventory_box(itr_box)
else
parent:iterate_inventory(itr_box)
end
for k,v in pairs(boxes_to_load) do
local ammo_box = level.object_by_id(k)
local loaded = ammo_box:ammo_get_count() > ammo_to_load and ammo_to_load or ammo_box:ammo_get_count()
local release_box = loaded == ammo_box:ammo_get_count()
for i=1,loaded do
stack.push(mag_data.loaded, ammo_sec)
end
if release_box then
ammo_to_load = ammo_to_load - ammo_box:ammo_get_count()
alife_release_id(k)
else
ammo_box:ammo_set_count(ammo_box:ammo_get_count() - loaded)
end
end
set_mag_data(magazine:id(), mag_data)
xr_sound.set_sound_play(AC_ID,"reload_shell")
timed_refresh()
end
function fast_unload_magazine(magazine)
local ammo_to_make = {}
local mag_data = get_mag_data(magazine:id())
while #mag_data.loaded > 0 do
local round = stack.pop(mag_data.loaded)
if not ammo_to_make[round] then
ammo_to_make[round] = 1
else
ammo_to_make[round] = ammo_to_make[round] + 1
end
end
set_mag_data(magazine:id(), mag_data)
local to_spawn = magazine:parent() and magazine:parent() or db.actor
for k,v in pairs(ammo_to_make) do
create_ammo(k, to_spawn:position(), to_spawn:level_vertex_id(), to_spawn:game_vertex_id(), to_spawn:id(), v)
create_time_event("ammo", "agg_"..magazine:id(), 0, function()
item_weapon.ammo_aggregation_full(to_spawn)
return true
end)
end
timed_refresh()
end
local load_cancel = {
[key_bindings.kQUIT] = true,
[key_bindings.kINVENTORY] = true
}
function on_before_key_press(key, bind, dis, flags)
-- do not interrupt if directional key pressed
if action_state ~= ACTION_NONE and (load_cancel[bind] or key == DIK_keys.DIK_LMENU) then
do_interrupt_magload()
end
end
function unload_ammo(obj)
if is_magazine(obj) then
local id = obj:id()
local mag_data = get_mag_data(id)
if mag_data and #mag_data.loaded > 0 then
return gc("st_mag_unload_ammo")
end
end
end
function func_unload_ammo(obj)
if not is_magazine(obj) then return end
local id = obj:id()
local mag_data = get_mag_data(id)
if not mag_data or #mag_data.loaded == 0 then return end
if parent_is_box(obj) or get_config("unload_behavior") == 1 then--get_config("fast_loading") and parent_is_box(obj) then
fast_unload_magazine(obj)
else
local delay = get_mag_time(obj:section(), true)
print_dbg("Start unload magazine at speed %s", delay)
action_start_magload()
RemoveTimeEvent("mag_redux", "unload_mag"..id)
create_time_event("mag_redux","unload_mag"..id, 0.1, unload_magazine, obj, delay * 1000)
end
end
function loadout_precondition(obj)
if is_magazine(obj) then
local id = obj:id()
if is_carried_mag(id) then
return game.translate_string("st_mag_loadout_remove")
elseif room_in_pouch(id) then
return game.translate_string("st_mag_loadout_add")
end
end
end
function loadout_func(obj)
if is_magazine(obj) then
local id = obj:id()
toggle_carried_mag(id)
inventory_refresh()
mag_hud.HUD:Update(true)
end
end
function retool_mag_precondition(obj)
if is_magazine(obj) and get_retool_section(obj) then
return game.translate_string("st_mag_retool")
end
end
function retool_mag_functor(obj)
local mag_data = get_mag_data(obj:id())
local tool = db.actor:object("leatherman_tool")
local retool_section = get_retool_section(obj)
if #mag_data.loaded > 0 then
actor_menu.set_item_news("fail", "detail", "st_mag_err_retool") --message to unload mag first
elseif not tool then
actor_menu.set_item_news('fail', 'weapon', "st_dis_text_3", " ")
else
new_obj = alife_create_item(retool_section, obj:parent())
if new_obj then
alife_release(obj)
utils_item.degrade( tool , 0) --change this
end
end
end
-----------------------------------------
-- SECTION weapon logic -----------------
-----------------------------------------
function actor_on_weapon_fired(obj, weapon, ammo_elapsed, grenade_elapsed, ammo_type, grenade_type)
weapon_unjammed(weapon) --this is only needed here for automatic shotguns, because they do not trigger the reload callback.
if not is_supported_weapon(weapon) or weapon:weapon_in_grenade_mode() then
return
end
prep_weapon(weapon, true)
end
function weapon_unjammed(weapon)
local weapon_id = weapon:id()
if jammed_guns[weapon_id] then
print_dbg("Removing jammed weapon entry %s", weapon_id)
jammed_guns[weapon_id] = nil
end
end
function weapon_jammed(weapon)
if arti_jamming then
print_dbg("WPO detected, not jamming")
return
end
print_dbg("Logging jammed weapon %s", weapon:id())
jammed_guns[weapon:id()] = true
end
function is_jammed_weapon(weapon)
return jammed_guns[weapon:id()] or false
end
-- Load next round into the weapon.
-- feed_next removes top round and feeds one after. otherwise feeds current
function prep_weapon(weapon, feed_next)
local id = weapon:id()
local mag_data = get_mag_loaded(id)
local ammo_map = get_ammo_map(id) --cacheing function should keep this from being too laggy, got to look out for the potatos
if mag_data then
local count = #mag_data.loaded
if feed_next then
stack.pop(mag_data.loaded)
end
local round = stack.peek(mag_data.loaded)
print_dbg("chambering round of type %s, there are %s rounds loaded, ammo in mag is %s", round, count, weapon:get_ammo_in_magazine())
if round ~= nil and ammo_map[round] then
weapon:set_ammo_type(ammo_map[round]-1)
-- weapon:unload_magazine()
weapon:set_ammo_elapsed(count)
elseif count ~= 0 then
weapon:set_ammo_elapsed(1)
print_dbg("mag has invalid ammo. If gun is not SVT this is a problem with the mag caliber entry")
else
print_dbg("mag empty")
end
end
set_mag_data(id, mag_data)
end
-- Reload mid animation with motion marks
-- Like vanilla, ignore name and force mag load on first motion mark
function actor_on_hud_animation_mark(state, mark)
if action_in_progress() and state == 7 then
force_reload = true
end
end
-----------------------------------------
-- SECTION UI callbacks -----------------
-----------------------------------------
local focus_last_sec
local focus_last_id
local focus_tbl = {}
function ActorMenu_on_item_focus_receive(obj) -- highlight compatible items
local sec_focus = obj:section()
local id = obj:id()
empty_table(focus_tbl)
if (is_supported_weapon(obj) and id ~= focus_last_id) then
focus_tbl = get_mags_for_basetype(get_weapon_base_type(obj))
elseif (is_magazine(sec_focus) and sec_focus ~= focus_last_sec) then
focus_tbl = get_magazine_caliber(sec_focus)
elseif (IsAmmo(obj) and sec_focus ~= focus_last_sec) then
focus_tbl = get_mags_by_ammo_type(sec_focus)
end
local inventory = GetActorMenu()
if not focus_tbl or not ((#focus_tbl > 0) or (inventory and inventory:IsShown())) then
return
end
for i=1,#focus_tbl do
inventory:highlight_section_in_slot(focus_tbl[i],EDDListType.iActorBag)
inventory:highlight_section_in_slot(focus_tbl[i],EDDListType.iPartnerTradeBag)
inventory:highlight_section_in_slot(focus_tbl[i],EDDListType.iDeadBodyBag)
inventory:highlight_section_in_slot(focus_tbl[i],EDDListType.iActorTrade)
inventory:highlight_section_in_slot(focus_tbl[i],EDDListType.iPartnerTrade)
end
end
function ActorMenu_on_item_before_move(flags, npc_id, obj, mode, bag)
flags.ret_value = flags.ret_value and not is_carried_mag(obj and obj:id() or 0)
end
function on_game_load()
-- reset action state
do_interrupt()
-- purge any errant time events
for k,v in pairs(time_event_cache) do
args = str_explode(k, ",")
RemoveTimeEvent(args[1], args[2])
end
end
function on_game_start()
on_option_change()
custom_functor_autoinject.add_functor("mags_load", check_eject_magazine, check_eject_magazine, nil, eject_magazine, true)
RegisterScriptCallback("ActorMenu_on_item_before_move", ActorMenu_on_item_before_move)
RegisterScriptCallback("ActorMenu_on_item_focus_receive", ActorMenu_on_item_focus_receive)
RegisterScriptCallback("on_before_key_press", on_before_key_press)
RegisterScriptCallback("actor_on_weapon_fired", actor_on_weapon_fired)
RegisterScriptCallback("actor_on_weapon_jammed", weapon_jammed)
RegisterScriptCallback("on_game_load", on_game_load)
RegisterScriptCallback("ActorMenu_on_item_drag_drop", on_item_drag_dropped)
RegisterScriptCallback("server_entity_on_unregister",se_item_on_unregister)
RegisterScriptCallback("on_option_change",on_option_change)
RegisterScriptCallback("actor_on_update", actor_on_update)
RegisterScriptCallback("actor_on_hud_animation_mark",actor_on_hud_animation_mark)
on_option_change()
end