Added New Mods and Profiles Folders

This is a complete rebuild of the modpack, with all new mods and updates for 1.5.3 of Anomaly.
This commit is contained in:
2025-01-14 05:07:53 -05:00
parent 85c665b107
commit 376b4b9689
21217 changed files with 546254 additions and 0 deletions
@@ -0,0 +1,12 @@
print_dbg = magazines.print_dbg
is_magazine = magazine_binder.is_magazine
GetCost = utils_item.get_item_cost
function utils_item.get_item_cost(obj, profile)
if not utils_item.on_get_item_cost and is_magazine(obj) and obj:parent() and obj:parent():id() == 0 then
print_dbg("reducing mag cost %s", obj:section())
return math.floor(GetCost(obj, profile) * 0.1)
else
return GetCost(obj, profile)
end
end
@@ -0,0 +1,101 @@
local keep_items
-- conditions to be kept in inventory
-- if returns true, keep the item
local conditions = {
function(item) return SYS_GetParam(1, item:section(), "quest_item") end,
function(item) return keep_items[item:section()] end,
function(item) if IsAmmo(item) and item_backpack.is_ammo_for_wpn(item:section()) then return true end end,
function(item) if db.actor:is_on_belt(item) or item_backpack.is_in_slot(item) then return true end end,
}
function add_condition(f)
table.insert(conditions, f)
end
function eval(item)
for _, cond in pairs(conditions) do
if cond(item) then return false end
end
return true
end
function item_backpack.actor_on_item_use(obj)
if not obj or (obj:section() ~= "itm_actor_backpack") then return end
local backpack = db.actor:item_in_slot(13)
if not backpack then
actor_menu.set_msg(1, game.translate_string("st_stash_no_backpack_found"),4)
return
end
local actor = db.actor
local se_obj = alife_create("inv_backpack",actor:position(),actor:level_vertex_id(),actor:game_vertex_id())
if (se_obj) then
local txt = strformat(game.translate_string("st_itm_stash_of_character"), db.actor:character_name())
level.map_add_object_spot_ser(se_obj.id, "treasure", txt)
actor_menu.set_msg(1, game.translate_string("st_stash_created"),4)
local m_data = alife_storage_manager.get_state()
if not (m_data.player_created_stashes) then
m_data.player_created_stashes = {}
end
m_data.player_created_stashes[se_obj.id] = backpack:section()
SendScriptCallback("actor_on_before_stash_create", backpack, se_obj)
local b_id = backpack:id()
alife_release(backpack)
local function transfer_items(id)
local obj = level.object_by_id(id)
if (obj) then
local function itr_inv(temp,item)
if item:id() ~= b_id and eval(item) then
db.actor:transfer_item(item,obj)
end
end
db.actor:iterate_inventory(itr_inv)
return true
end
return false
end
CreateTimeEvent(0,"actor_backpack",0,transfer_items,se_obj.id)
end
end
function item_backpack.UICreateStash:OnAccept()
local se_obj = alife_create("inv_backpack",db.actor:position(),db.actor:level_vertex_id(),db.actor:game_vertex_id())
if (se_obj) then
local txt = self.input:GetText()
txt = txt ~= "" and txt or strformat(game.translate_string("st_itm_stash_of_character"), db.actor:character_name())
level.map_add_object_spot_ser(se_obj.id, "treasure", txt)
actor_menu.set_msg(1, game.translate_string("st_stash_created"),4)
local m_data = alife_storage_manager.get_state()
if not (m_data.player_created_stashes) then
m_data.player_created_stashes = {}
end
SendScriptCallback("actor_on_before_stash_create", get_object_by_id(self.id), se_obj)
m_data.player_created_stashes[se_obj.id] = self.section
alife_release_id(self.id)
local data = {
stash_id = se_obj.id,
stash_name = txt,
stash_section = self.section,
}
SendScriptCallback("actor_on_stash_create",data)
end
self:Close()
end
function on_game_start()
local ini_stash = ini_file("items\\settings\\backpack_stash.ltx")
keep_items = utils_data.collect_section(ini_stash,"actor_backpack_keep_items",true)
AddScriptCallback("actor_on_before_stash_create")
end
@@ -0,0 +1,320 @@
-- AmmoCheck
-- Last modified: 2022.08.17
-- https://github.com/RAX-Anomaly/AmmoCheck
-- settings--
local use_clr = false
local hide_counter = true
local hide_ammo_icon = true
local busy_hands_fix = false
local mcm_key = DIK_keys.DIK_T
-- locals --
local clr00_Red = GetARGB(0xff, 0xff, 0x00, 0x00)
local clr01_RedOrange = GetARGB(0xff, 0xff, 0x40, 0x00)
local clr02_Orange = GetARGB(0xff, 0xff, 0x80, 0x00)
local clr03_Amber = GetARGB(0xff, 0xff, 0xc0, 0x00)
local clr04_LemonGlacier = GetARGB(0xff, 0xff, 0xff, 0x00)
local clr05_LaserLemon = GetARGB(0xff, 0xff, 0xff, 0x80)
local clr06_White = GetARGB(0xff, 0xff, 0xff, 0xff)
local clrEE_Purple = utils_xml.get_color("d_purple", true)
-- the misalignment of the colors and the text means 2x the info without 2x the text.
local messages = {
{ m = "st_ac_near_empty", c = clr00_Red }, -- <1/10
{ m = "st_ac_near_empty", c = clr01_RedOrange }, -- <2/10
{ m = "st_ac_less_half", c = clr01_RedOrange }, -- <3/10
{ m = "st_ac_less_half", c = clr02_Orange }, -- <4/10
{ m = "st_ac_about_half", c = clr02_Orange }, -- <5/10
{ m = "st_ac_about_half", c = clr03_Amber }, -- <6/10
{ m = "st_ac_more_half", c = clr03_Amber }, -- <7/10
{ m = "st_ac_more_half", c = clr04_LemonGlacier }, -- <8/10
{ m = "st_ac_nearly_full", c = clr04_LemonGlacier }, -- <9/10
{ m = "st_ac_nearly_full", c = clr05_LaserLemon }, -- <10/10
{ m = "st_ac_full", c = clr06_White } -- full
}
local dbg_log
function print_dbg(msg, ...)
if not mcm_log then
printf("![AC] " .. msg, ...)
elseif not ui_mcm.MCM_DEBUG then -- use MCM's debug log setting
return
else
if not dbg_log then
dbg_log = mcm_log.new("![AC]")
dbg_log.enabled = true
end
if dbg_log then dbg_log:log(msg, ...) end
end
end
function null_function()
return false
end
function get_mag_loaded_shim(id)
give_news("You are using an older version of MagsRedux. Update to Github version for best experience.")
local mag_data = get_data(id)
return (mag_data and mag_data.section ~= "no_mag") and mag_data or nil
end
local mcm_keybinds = ui_mcm and ui_mcm.key_hold
local modifier = 0
local mode = 0
local modes = {
[0] = { ["call"] = { "on_key_press", "on_key_hold" }, ["function"] = function(key) ui_mcm.simple_press("rax_ammo_check", key, check_Ammo) end },
[1] = { ["call"] = { "on_key_press", "on_key_hold" }, ["function"] = function(key) if ui_mcm.double_tap("rax_ammo_check", key) then check_Ammo() end end },
[2] = { ["call"] = { "on_key_hold", "on_key_press" }, ["function"] = function(key) if ui_mcm.key_hold("rax_ammo_check", key) then check_Ammo() end end }
}
local direction_keys = {
[key_bindings.kFWD] = true,
[key_bindings.kBACK] = true,
[key_bindings.kL_STRAFE] = true,
[key_bindings.kACCEL] = true,
[key_bindings.kR_STRAFE] = true
}
local weapon_hidden = false
function on_key_press(key)
-- do not interrupt if directional key pressed
local bind = dik_to_bind(key)
if weapon_hidden and not direction_keys[bind] then
weapon_hidden = false
end
if key ~= mcm_key then return end
if not mcm_keybinds then
check_Ammo()
return
end
if ui_mcm.get_mod_key(modifier) then
modes[mode]["function"](key)
end
end
function on_key_hold(key)
if key ~= mcm_key then return end
if ui_mcm.get_mod_key(modifier) then
modes[mode]["function"](key)
end
end
function l_round(value)
local min = math.floor(value + 0.5)
return min
end
function on_game_start()
-- aliases--
gc = game.translate_string
if magazine_binder then
get_data = magazine_binder.get_data
set_data = magazine_binder.set_data
get_mag_loaded = magazine_binder.get_mag_loaded or get_mag_loaded_shim -- get_mag_loaded does not exist in moddb version. MagsRedux needs update.
is_supported_weapon = magazine_binder.is_supported_weapon
is_jammed_weapon = magazines.is_jammed_weapon
get_sec_chambered = magazines.get_sec_chambered
print_dbg("MagsRedux installed. Working in integrated mode.")
else
get_data = null_function -- Should not be called, just in case.
set_data = null_function -- Should not be called, just in case.
is_supported_weapon = null_function -- Always returns false, meaning none of the weapons are supported, fall back on vanilla ammo handling.
get_mag_loaded = null_function -- There is never a loaded magazine in vanilla.
is_jammed_weapon = null_function -- Sadly, standalone mode does not report weapon jams for now.
get_sec_chambered = null_function -- No "one-in-the-chamber" feature in vanilla.
print_dbg("MagsRedux not found. Working in standalone mode.")
end
RegisterScriptCallback("actor_on_first_update", actor_on_first_update)
RegisterScriptCallback("on_option_change", on_option_change)
RegisterScriptCallback("on_option_change", on_option_change)
RegisterScriptCallback("on_key_press", on_key_press)
end
-- Script Callbacks --
function on_mcm_load()
ch_options = {
id = "rax_ammo_check",
sh = true,
gr = {
{ id = "ammo_check", type = "slide", link = "ui_options_slider_player", text = "ui_mm_title_rax_ammo_check", size = { 512, 50 }, spacing = 20 },
{ id = "usecolor", type = "check", val = 1, def = false },
{ id = "hidecounter", type = "check", val = 1, def = true },
{ id = "hideicon", type = "check", val = 1, def = true },
{ id = "busy_hands_fix", type = "check", val = 1, def = false },
{ id = "keybind", type = "key_bind", val = 2, def = DIK_keys.DIK_F },
{ id = "modifier", type = ui_mcm.kb_mod_radio, val = 2, def = 0, hint = "mcm_kb_modifier", content = { { 0, "mcm_kb_mod_none" }, { 1, "mcm_kb_mod_shift" }, { 3, "mcm_kb_mod_alt" } } }, --I removed control from the list, note the values of the other options were unchanged.
{ id = "mode", type = ui_mcm.kb_mod_radio, val = 2, def = 2, hint = "mcm_kb_mode", content = { { 0, "mcm_kb_mode_press" }, { 1, "mcm_kb_mode_dtap" }, { 2, "mcm_kb_mode_hold" } } },
{ id = "desc_mcm", type = "desc", text = "ui_mcm_rax_ammo_check_update_mcm", clr = { 255, 175, 0, 0 }, precondition = { function() return not mcm_keybinds end } },
}
}
return ch_options
end
function actor_on_first_update()
on_option_change()
end
function on_option_change()
if ui_mcm then
hide_counter = ui_mcm.get("rax_ammo_check/hidecounter")
use_clr = ui_mcm.get("rax_ammo_check/usecolor")
hide_ammo_icon = ui_mcm.get("rax_ammo_check/hideicon")
busy_hands_fix = ui_mcm.get("rax_ammo_check/busy_hands_fix")
if mcm_keybinds then
mcm_key = ui_mcm.get("rax_ammo_check/keybind")
mode = ui_mcm.get("rax_ammo_check/mode")
modifier = ui_mcm.get("rax_ammo_check/modifier")
RegisterScriptCallback(modes[mode]["call"][1], this[modes[mode]["call"][1]])
UnregisterScriptCallback(modes[mode]["call"][2], this[modes[mode]["call"][2]])
end
end
pos = ActorMenu.get_maingame().m_ui_hud_states.m_ui_weapon_cur_ammo:GetWndPos()
pos.x = ((hide_counter and pos.x > 0) or ((not hide_counter) and pos.x < 0)) and (-1 * pos.x) or pos.x
ActorMenu.get_maingame().m_ui_hud_states.m_ui_weapon_cur_ammo:SetWndPos(pos)
pos = ActorMenu.get_maingame().m_ui_hud_states.m_ui_weapon_icon:GetWndPos()
pos.x = ((hide_ammo_icon and pos.x > 0) or ((not hide_ammo_icon) and pos.x < 0)) and (-1 * pos.x) or pos.x
ActorMenu.get_maingame().m_ui_hud_states.m_ui_weapon_icon:SetWndPos(pos)
end
-- Main function --
function checkAmmo() end --crash prevention for dirty updates.
function check_Ammo()
local weapon = db.actor:active_item()
-- ends if no weapon is in hand etc.
if (weapon == nil or (not IsWeapon(weapon)) or IsItem("fake_ammo_wpn", nil, weapon)) then
return
end
local weaponId = weapon:id()
local message = ""
local clr = nil
local currentState = weapon:get_state()
if not (currentState == 0 or weapon:weapon_in_grenade_mode()) then
return
end
-- if magazine weapon
local currentAmmo = weapon:get_ammo_in_magazine()
if (currentAmmo == nil) then
return
end
local sec = weapon:section()
local mag_data = get_mag_loaded(weaponId)
-- empty/gl use cases
if (is_jammed_weapon(weapon)) then
message = gc("st_ac_jammed")
clr = use_clr and clr00_Red or nil
elseif weapon:weapon_in_grenade_mode() then
message = currentAmmo == 1 and gc("st_ac_grenade") or gc("st_ac_empty")
clr = use_clr and (currentAmmo == 1 and clr06_White or clr00_Red) -- grenade or no grenade that is the question
elseif currentAmmo == 0 then
if is_supported_weapon(sec) and not mag_data then
message = gc("st_ac_noMag")
clr = use_clr and clr00_Red or nil
else
message = gc("st_ac_empty")
clr = use_clr and clr00_Red or nil
end
else
local top_round = nil
local max_ammo = 0
if not mag_data and is_supported_weapon(sec) then
-- some random crap to force it to say oitc
currentAmmo = 1
max_ammo = 10
elseif is_supported_weapon(sec) then
print_dbg("checking for section %s", mag_data.section)
max_ammo = SYS_GetParam(2, mag_data.section, "max_mag_size")
top_round = magazines_mcm.get_config("retain_round") and mag_data.loaded[#mag_data.loaded - 1] or stack.peek(mag_data.loaded)
else
top_round = get_sec_chambered(weapon)
max_ammo = SYS_GetParam(2, sec, "ammo_mag_size")
end
local curAmmoPerc = currentAmmo / max_ammo
if currentAmmo == max_ammo + 1 then
message = gc("st_ac_plus1")
clr = use_clr and clr06_White -- fullest
elseif (curAmmoPerc > 1) then
message = gc("st_ac_overfull")
clr = clrEE_Purple -- always colored this is an error.
elseif (currentAmmo == 1 and SYS_GetParam(2, sec, "ammo_mag_size", 0) == 2) then
message = gc("st_ac_just_one")
clr = use_clr and clr03_Amber -- last shell in double-barreled shotty
elseif currentAmmo == 1 and not mag_data and is_supported_weapon(sec) then
message = gc("st_ac_oitc")
clr = use_clr and clr00_Red -- last round in the chamber with no maggie
else
idx = l_round((curAmmoPerc * 10) + .5) -- gives an integer value n for the decile of curAmmoPerc and 11 for full.
print_dbg("IDX is %s %s %s %s", idx, curAmmoPerc, currentAmmo, max_ammo)
message = messages[idx] and gc(messages[idx].m) or "Error!"
clr = use_clr and messages[idx] and messages[idx].c or nil
end
if curAmmoPerc > 0 and top_round then
message = message .. ", " .. gc(ui_item.get_sec_short_name(top_round))
end
end
disable_info("sleep_active")
if not busy_hands_fix then
local slot = db.actor:active_slot()
db.actor:activate_slot(0)
weapon_hidden = true
CreateTimeEvent("ammo_check", "restore Weapon", 0, function(slot)
if db.actor:active_item() then
return false
end
if not weapon_hidden then
db.actor:activate_slot(slot)
weapon_hidden = false
return true
end
actor_menu.set_msg(1, message, 2, clr)
db.actor:activate_slot(slot)
return true
end
, slot)
else
CreateTimeEvent("ammo_check", "message delay", 1.5, function()
actor_menu.set_msg(1, message, 2, clr)
return true
end)
end
end
local old_news = {}
function give_news(message)
if old_news[message] then return end
old_news[message] = true
print_dbg(message)
if db.actor then
db.actor:give_game_news("AmmoCheck", message, "ui_inGame2_D_Ohotnik_na_mutantov", 0, 5000, 0)
end
end
@@ -0,0 +1,4 @@
function bind_stalker_ext.actor_on_weapon_jammed(binder, actor, wpn)
SendScriptCallback("actor_on_weapon_jammed", wpn)
end
@@ -0,0 +1,538 @@
--[[
Custom Dynamic Functors, written by demonized
Allows to add/remove item functors dynamically from script
Can override ltx-defined functors and can remove the override to return back to ltx functor
To use in your script look at example below
---------------------------------------------------------------------------------------------------------------------
local function name_condition_function(obj, bag, mode)
if obj then
return true
end
end
local function name_function(obj, bag, mode)
return "st_my_functor_string_in_xml"
end
local function action_condition_function(obj, bag, mode)
if obj then
return true
end
end
local function action_function(obj, bag, mode)
alife_create_item(obj:section(), db.actor)
end
local add_functor = custom_functor_autoinject.add_functor
add_functor("my_name_of_functor", name_condition_function, name_function, action_condition_function, action_function, override_bags<true, false>)
---------------------------------------------------------------------------------------------------------------------
to add your functor you use add_functor function which requires these arguments in order:
name: string, your name of your functor, can be any string.
If the name of functor already exists this function will overwrite functions for it
name_condition_function: function, the condition at which you will get your right-click option for item
name_function: function, the name itself, must return string ID defined in XML files for your option.
if set to return nil, the option won't appear
action_condition_function: function, the condition at which the action will be performed, usually its the same as name_condition_function
you can put nil into argument to use the same function as name_condition_function
action_function: function, the action itself to perform
override_bags: boolean. If its true, then you can override bags and modes to check for condition, otherwise it will use defaults (mode == "inventory" and bag == "actor_bag" or bag == "actor_equ" or bag == "actor_belt")
functions themselves accept obj, bag and mode arguments
obj: current object you right-clicked
bag: current bag, list of possible bags: {"actor_equ","actor_belt","actor_bag","actor_trade_bag","actor_trade","npc_bag","npc_trade","npc_trade_bag"}
mode: current mode, list of possible modes: {"inventory" , "loot" , "trade" , "repair"}
bag and mode is not enabled unless you set override_bags flag to true
removal of functor is done by calling this
--------------------------------------------------------------------------------------
local remove_functor = custom_functor_autoinject.remove_functor
remove_functor("my_name_of_functor")
--------------------------------------------------------------------------------------
you can also override existing functors defined in item's ltx by using this
---------------------------------------------------------------------------------------------------------------------
local override_functor = custom_functor_autoinject.override_functor
override_functor(slot, name_condition_function, name_function, action_condition_function, action_function, override_bags<true, false>)
---------------------------------------------------------------------------------------------------------------------
arguments are same, except you have to provide the slot (first argument) for your override. The slots are 1-10
for example if item's ltx have use1_functor and use1_action_functor then
if you want to override it you have to provide slot 1
be aware that if your name_condition_function or action_condition_function may return false
then old functor will be fired
if you don't want that behaviour, you can define generic "return true" function for those
and check condition for firing in name_function and action_function
removal of override is done by calling this
--------------------------------------------------------------------------------------
local remove_override = custom_functor_autoinject.remove_override
remove_override(slot)
--------------------------------------------------------------------------------------
--]]
local function func_index(t,a,b)
return (t[a].index) < (t[b].index)
end
local function func_index_reverse(t,a,b)
return (t[a].index) > (t[b].index)
end
local function func_value(t, a, b)
return t[a] < t[b]
end
local spairs = spairs
local string_find = string.find
local string_gsub = string.gsub
local table_remove = table.remove
local tonumber = tonumber
local unpack = unpack
--Recursive print of tables similar to PHP print_r function
local function print_r(t)
local print_r_cache={}
local function sub_print_r(t,indent)
if (print_r_cache[tostring(t)]) then
printf(indent.."*"..tostring(t))
else
print_r_cache[tostring(t)]=true
if (type(t)=="table") then
for pos,val in pairs(t) do
if (type(val)=="table") then
printf(indent.."["..pos.."] => "..tostring(t).." {")
sub_print_r(val,indent..string.rep(" ",string.len(pos)+8))
printf(indent..string.rep(" ",string.len(pos)+6).."}")
else
printf(indent.."["..pos.."] => "..tostring(val))
end
end
else
printf(indent..tostring(t))
end
end
end
sub_print_r(t," ")
end
local function print_table(table, subs)
local sub
if subs ~= nil then
sub = subs
else
sub = ""
end
for k,v in pairs(table) do
if type(v) == "table" then
print_table(v, sub.."["..k.."]----->")
elseif type(v) == "function" then
printf(sub.."%s = function",k)
elseif type(v) == "userdata" then
if (v.x) then
printf(sub.."%s = %s",k,utils_data.vector_to_string(v))
else
printf(sub.."%s = userdata", k)
end
elseif type(v) == "boolean" then
if v == true then
if(type(k)~="userdata") then
printf(sub.."%s = true",k)
else
printf(sub.."userdata = true")
end
else
if(type(k)~="userdata") then
printf(sub.."%s = false", k)
else
printf(sub.."userdata = false")
end
end
else
if v ~= nil then
printf(sub.."%s = %s", k,v)
else
printf(sub.."%s = nil", k,v)
end
end
end
end
-- Removing element from table and shifting down both key and value
-- Modes: 0 - index, 1 - value, 2 - key, 3 - key-index
local function table_remove_shift(t, val, mode)
local removed = false
local res
if mode == 0 then
for k, v in spairs(t, func_index) do
if removed then
t[k - 1] = v
if t[k - 1].index then
t[k - 1].index = t[k - 1].index - 1
end
if t[k - 1].properties_index then
t[k - 1].properties_index = t[k - 1].properties_index - 1
end
t[k] = nil
elseif v.index and v.index == val then
res = t[k]
t[k] = nil
removed = true
end
end
elseif mode == 1 then
for k, v in spairs(t, func_value) do
if removed then
t[k - 1] = v - 1
t[k] = nil
elseif v == val then
res = t[k]
t[k] = nil
removed = true
end
end
elseif mode == 2 then
for k, v in spairs(t, func_value) do
if removed then
t[k] = v - 1
elseif v == val then
res = t[k]
t[k] = nil
removed = true
end
end
elseif mode == 3 then
for k, v in spairs(t, func_index) do
if removed then
if t[k].index then
t[k].index = t[k].index - 1
end
elseif v.index and v.index == val then
res = t[k]
t[k] = nil
removed = true
end
end
end
return res
end
local ui_inventory_init = ui_inventory.UIInventory.__init
ui_inventory.UIInventory.__init = function(self)
ui_inventory_init(self)
self.custom_functor = {}
self.custom_functor_names = {}
end
local NameCustom = ui_inventory.UIInventory.Name_Custom
function ui_inventory.UIInventory:Name_Custom(obj, bag, temp, i)
obj = self:CheckItem(obj,"Name_Custom " .. i)
if self.custom_functor[i] and self.custom_functor[i].cond_name(obj, bag, self.mode) then
return self.custom_functor[i].func_name(obj, bag, self.mode)
else
return NameCustom(self, obj, bag, temp, i)
end
end
local ActionCustom = ui_inventory.UIInventory.Action_Custom
function ui_inventory.UIInventory:Action_Custom(obj, bag, temp, i)
obj = self:CheckItem(obj,"Action_Custom " .. i)
if self.custom_functor[i] and self.custom_functor[i].cond_action(obj, bag, self.mode) then
return self.custom_functor[i].func_action(obj, bag, self.mode)
else
return ActionCustom(self, obj, bag, temp, i)
end
end
ui_inventory.UIInventory.get_max_custom_functor = function(self)
local max = 0
local max_custom = {}
local max_index = 0
for k, v in pairs(self.properties) do
if string_find(k, "custom_.*") then
if v.index > max_index then
max_index = v.index
end
local x = tonumber(string_gsub(k, "custom_(.*)", "%1"), nil)
if x > max then
max = x
max_custom = v
end
end
end
return max, max_custom, max_index
end
local modes = {
["inventory"] = true,
["loot"] = true,
["trade"] = true,
["repair"] = true
}
local bags = {
["actor_equ"] = true,
["actor_belt"] = true,
["actor_bag"] = true,
["actor_trade_bag"] = true,
["actor_trade"] = true,
["npc_bag"] = true,
["npc_trade"] = true,
["npc_trade_bag"] = true
}
ui_inventory.UIInventory.Mode_Custom_Functor = function(self, obj, bag, temp, i)
return modes[self.mode]
end
ui_inventory.UIInventory.Cont_Custom_Functor = function(self, obj, bag, temp, i)
return bags[bag]
end
ui_inventory.UIInventory.add_custom_functor = function(self, name, cond_name, func_name, cond_action, func_action, override_bags)
local custom_functor_slot
if self.custom_functor_names[name] then
custom_functor_slot = self.custom_functor_names[name]
self.custom_functor[custom_functor_slot].cond_name = cond_name
self.custom_functor[custom_functor_slot].func_name = func_name
self.custom_functor[custom_functor_slot].cond_action = cond_action
self.custom_functor[custom_functor_slot].func_action = func_action
self.properties["custom_" .. custom_functor_slot].mode_func[1] = override_bags and "Mode_Custom_Functor" or "Mode_Custom"
self.properties["custom_" .. custom_functor_slot].cont_func[1] = override_bags and "Cont_Custom_Functor" or "Cont_Custom"
else
local max, max_custom, max_index = self:get_max_custom_functor()
local custom_num = max + 1
local properties_num = max_index + 1
for k, v in spairs(self.properties, func_index_reverse) do
if v.index > max_index then
printf("%s, %s", k, v.index)
v.index = v.index + 1
else
printf("%s, %s, max custom_functor reached", k, v.index)
break
end
end
self.properties["custom_" .. custom_num] = {
index = properties_num,
name_func = {"Name_Custom", max_custom["name_func"][2] + 1},
mode_func = {override_bags and "Mode_Custom_Functor" or "Mode_Custom", max_custom["mode_func"][2] + 1},
cont_func = {override_bags and "Cont_Custom_Functor" or "Cont_Custom", max_custom["cont_func"][2] + 1},
precondition1 = {"Name_Custom", max_custom["precondition1"][2] + 1},
action = {"Action_Custom", max_custom["action"][2] + 1}
}
custom_functor_slot = custom_num
self.custom_functor[custom_functor_slot] = {
index = custom_functor_slot,
properties_index = properties_num,
name = name,
cond_name = cond_name,
func_name = func_name,
cond_action = cond_action,
func_action = func_action
}
self.custom_functor_names[name] = custom_functor_slot
end
end
ui_inventory.UIInventory.remove_custom_functor = function(self, name)
if not self.custom_functor_names[name] then return end
local index = table_remove_shift(self.custom_functor_names, self.custom_functor_names[name], 2)
if not index then return end
local custom_functor = table_remove_shift(self.custom_functor, index, 0)
if not custom_functor then return end
local removed = false
for k, v in spairs(self.properties, func_index) do
if string_find(k, "custom_.*") then
if removed then
local x = tonumber(string_gsub(k, "custom_(.*)", "%1"), nil) - 1
self.properties["custom_" .. x] = self.properties[k]
self.properties["custom_" .. x].index = self.properties["custom_" .. x].index - 1
self.properties["custom_" .. x].name_func[2] = self.properties["custom_" .. x].name_func[2] - 1
self.properties["custom_" .. x].mode_func[2] = self.properties["custom_" .. x].mode_func[2] - 1
self.properties["custom_" .. x].cont_func[2] = self.properties["custom_" .. x].cont_func[2] - 1
self.properties["custom_" .. x].precondition1[2] = self.properties["custom_" .. x].precondition1[2] - 1
self.properties["custom_" .. x].action[2] = self.properties["custom_" .. x].action[2] - 1
self.properties[k] = nil
elseif v.index == custom_functor.properties_index then
self.properties[k] = nil
removed = true
end
elseif removed then
v.index = v.index - 1
end
end
end
ui_inventory.UIInventory.override_functor = function(self, slot, cond_name, func_name, cond_action, func_action, override_bags)
self.custom_functor[slot] = {
cond_name = cond_name,
func_name = func_name,
cond_action = cond_action,
func_action = func_action
}
self.properties["custom_" .. slot].mode_func[1] = override_bags and "Mode_Custom_Functor" or "Mode_Custom"
self.properties["custom_" .. slot].cont_func[1] = override_bags and "Cont_Custom_Functor" or "Cont_Custom"
end
ui_inventory.UIInventory.remove_override = function(self, slot)
self.custom_functor[slot] = nil
self.properties["custom_" .. slot].mode_func[1] = "Mode_Custom"
self.properties["custom_" .. slot].cont_func[1] = "Cont_Custom"
end
ui_inventory.UIInventory.get_functor_at_slot = function(self, slot)
return self.custom_functor[slot]
end
ui_inventory.UIInventory.get_functor_by_name = function(self, name)
if self.custom_functor_names[name] then
return self.custom_functor[self.custom_functor_names[name]], self.custom_functor_names[name]
end
end
-- Adding
local first_update_pending = true
local functor_queue = {}
local function add_to_queue(type, ...)
functor_queue[#functor_queue + 1] = {
type = type,
data = {...}
}
end
local function process_queue()
if not ui_inventory.GUI then
ui_inventory.GUI = ui_inventory.UIInventory()
end
printf("Custom functors, processing queue")
for i, v in ipairs(functor_queue) do
printf("Custom functors, type %s", v.type)
ui_inventory.GUI[v.type](ui_inventory.GUI, unpack(v.data))
end
functor_queue = {}
first_update_pending = false
end
local function actor_on_first_update()
process_queue()
end
function add_functor(name, cond_name, func_name, cond_action, func_action, override_bags)
if not name then return end
local cond_action = cond_action or cond_name
if first_update_pending then
add_to_queue("add_custom_functor", name, cond_name, func_name, cond_action, func_action, override_bags)
return
end
ui_inventory.GUI:add_custom_functor(name, cond_name, func_name, cond_action, func_action, override_bags)
end
function remove_functor(name)
if not name then return end
if first_update_pending then
add_to_queue("remove_custom_functor", name)
return
end
ui_inventory.GUI:remove_custom_functor(name)
end
function override_functor(slot, cond_name, func_name, cond_action, func_action, override_bags)
if slot < 1 or slot > 10 then
printf("functor slot is not valid, min 1, max 10, your slot: %s", slot)
return
end
local cond_action = cond_action or cond_name
if first_update_pending then
add_to_queue("override_functor", slot, cond_name, func_name, cond_action, func_action, override_bags)
return
end
ui_inventory.GUI:override_functor(slot, cond_name, func_name, cond_action, func_action, override_bags)
end
function remove_override(slot)
if slot < 1 or slot > 10 then
printf("functor slot is not valid min 1, max 10, your slot: %s", slot)
return
end
if first_update_pending then
add_to_queue("remove_override", slot)
return
end
ui_inventory.GUI:remove_override(slot)
end
function get_functor_at_slot(slot)
if first_update_pending then
add_to_queue("get_functor_at_slot", slot)
return
end
return ui_inventory.GUI:get_functor_at_slot(slot)
end
function get_functor_by_name(name)
if first_update_pending then
add_to_queue("get_functor_by_name", name)
return
end
return ui_inventory.GUI:get_functor_by_name(name)
end
function print_properties()
if not ui_inventory.GUI then
ui_inventory.GUI = ui_inventory.UIInventory()
end
for k, v in spairs(ui_inventory.GUI.properties, func_index) do
printf("[" .. k .. "] => ")
print_r(v)
end
end
function print_custom_functor()
if not ui_inventory.GUI then
ui_inventory.GUI = ui_inventory.UIInventory()
end
print_r(ui_inventory.GUI.custom_functor)
end
function print_custom_functor_names()
if not ui_inventory.GUI then
ui_inventory.GUI = ui_inventory.UIInventory()
end
print_r(ui_inventory.GUI.custom_functor_names)
end
function on_game_start()
RegisterScriptCallback("actor_on_first_update", actor_on_first_update)
end
@@ -0,0 +1,25 @@
-- Customize to your liking.
local mag_bars_custom = true
local mag_bars_background = true
local mag_bars_color = GetARGB(255, 200, 200, 200)
-- Stop customizing.
local Base_Add_ProgressBar = utils_ui.UICellItem.Add_ProgressBar
local is_magazine = magazine_binder.is_magazine
utils_ui.UICellItem.Add_ProgressBar = function(sender, xml, obj, sec, clsid)
Base_Add_ProgressBar(sender, xml, obj, sec, clsid)
if mag_bars_custom and sender.bar and is_magazine(sec) then
sender.bar:ShowBackground(mag_bars_background)
sender.bar:SetColor(mag_bars_color)
end
end
local vanilla_bar_list_for_reference_not_used_here = {
["condition_progess_bar"] = {min = {255, 196, 18, 18, 0}, mid = {255, 255, 255, 118, 0.5}, max = {255, 107, 207, 119, 1}, background = true},
["power_progess_bar"] = {def = GetARGB(255, 86, 196, 209), background = true},
["uses_progess_bar"] = {def = GetARGB(255, 255, 255, 255), background = false}
}
@@ -0,0 +1,422 @@
--[[
RavenAscendant
07May2021
Ready Magazine HUD for Anomaly Magazine rewrite.
--]]
local get_mag_property = magazine_binder.get_mag_property
local get_size = magazine_binder.get_size
local is_carried_mag = magazine_binder.is_carried_mag
local print_dbg = function (...) return false and magazine_binder.print_dbg(...) end
local get_magazine_base_type = magazine_binder.get_magazine_base_type
local gc = game.translate_string
-- Update rate
local tg_update_step = 1000 --[ms]
local scale = 1
local ammo_scale = 1
local group_by_ammo = true
local grp_by_bt = true
local show_bgrd
----------------------------------
-- HUD (Indicators)
----------------------------------
HUD = nil
-------
function on_screen_resolution_changed()
deactivate_hud()
activate_hud()
end
function activate_hud()
RegisterScriptCallback("actor_on_net_destroy",actor_on_net_destroy)
RegisterScriptCallback("on_console_execute",on_console_execute)
RegisterScriptCallback("GUI_on_show",update_hud)
RegisterScriptCallback("GUI_on_hide",update_hud)
if HUD == nil then
HUD = UIMagHUD()
get_hud():AddDialogToRender(HUD)
end
HUD:Update(true)
end
function deactivate_hud()
if HUD ~= nil then
get_hud():RemoveDialogToRender(HUD)
HUD = nil
end
UnregisterScriptCallback("actor_on_net_destroy",actor_on_net_destroy)
UnregisterScriptCallback("on_console_execute",on_console_execute)
UnregisterScriptCallback("GUI_on_show",update_hud)
UnregisterScriptCallback("GUI_on_hide",update_hud)
end
function update_hud()
if HUD ~= nil then
HUD:Update(true)
end
end
function actor_on_net_destroy()
if HUD ~= nil then
get_hud():RemoveDialogToRender(HUD)
HUD = nil
end
end
function on_console_execute(name)
if name == "hud_draw" and HUD then
HUD:Update(true)
end
end
local function adjust_vert(ele, bottom)
local new_y
local pos = ele:GetWndPos()
if bottom then
new_y = 768 - ((768 - pos.y)*scale)
else
new_y = pos.y * scale
end
pos.y = new_y
ele:SetWndPos( pos)
end
-------
class "UIMagHUD" (CUIScriptWnd)
function UIMagHUD:__init() super()
self.mirrored = true
self.bottom = true
self.ratio = utils_xml.screen_ratio()
self._tmr = time_global()
self.offset = 0
self.w = 1
self.mags = {}
self.mags.small = {}
self.mags.medium = {}
self.mags.large = {}
self:InitControls()
self:Update(true)
end
function UIMagHUD:__finalize()
end
local xml
function UIMagHUD:InitControls()
xml = CScriptXmlInit()
xml:ParseFile("mag_hud.xml")
self.dialog = xml:InitStatic("pouch", self)
adjust_vert(self.dialog, self.bottom)
self.pouch = {}
self.pouch.small = xml:InitStatic("pouch:small", self.dialog)
adjust_vert(self.pouch.small)
self.pouch.small:Show(true)
self.pouch.medium = xml:InitStatic("pouch:medium", self.dialog)
adjust_vert(self.pouch.medium)
self.pouch.medium:Show(true)
self.pouch.large = xml:InitStatic("pouch:large", self.dialog)
adjust_vert(self.pouch.large)
self.pouch.large:Show(true)
self.dialog:Show(true)
self.bkg = {}
self.bkg.small = xml:InitStatic("mag", self.pouch.small)
local bkg_t = "ui_mags_bkg" .. magazines_mcm.get_config("bgrd")
self.bkg.small:InitTexture( bkg_t )
self.bkg.medium = xml:InitStatic("mag", self.pouch.medium)
self.bkg.medium:InitTexture( bkg_t )
self.bkg.large = xml:InitStatic("mag", self.pouch.large)
self.bkg.large:InitTexture( bkg_t )
end
function UIMagHUD:Clear()
self.dialog:Show(false)
self.bkg.small:Show(false)
self.bkg.medium:Show(false)
self.bkg.large:Show(false)
for a, b in pairs(self.mags) do
for k, ele in pairs(b) do
ele:Show(false)
end
end
end
function UIMagHUD:Update(force) --Update(true) can be called from weapon reload script to force an immediate update. Will also be called when inventory closes. otherwise a periodic update od once a second happens. that is probably exsesive as long as update is happening on run reload.
CUIScriptWnd.Update(self)
local tg = time_global()
if force then
self._tmr = tg - 1
end
if self._tmr >= tg then
return
else
self._tmr = tg + tg_update_step
end
-- Clear all
self:Clear()
-- Hide HUD when it's occupied by a GUI class
if not (main_hud_shown() or force) then
return
end
local mags = {small = {}, medium = {}, large = {}}
function itr(item)
id = item:id()
if is_carried_mag(id) then
local section = get_mag_property(id, "section")
local base_type = get_magazine_base_type(id)
local size = get_size(id)
local loaded = get_mag_property(id, "loaded")
local round = stack.peek(loaded)
local grp = grp_by_bt and base_type or section
--print_dbg("itr bt %s , sec %s , grp %s",base_type, section, grp)
local ammo = group_by_ammo and round or "none"
mags[size][grp] = mags[size][grp] or {}
mags[size][grp][ammo] = mags[size][grp][ammo] or {}
mags[size][grp][ammo][#mags[size][grp][ammo]+1] = section
--magazine_binder.dbg:log_table(mags[size][grp][ammo],"mags[size][grp][ammo]")
end
end
db.actor:inventory_for_each(itr)
for size, mag_cell_list in pairs(self.mags) do
for i, mag_cell in ipairs(mag_cell_list) do
mags[size][mag_cell.grp] = mags[size][mag_cell.grp] or {}
--if mags[size][mag_cell.grp][mag_cell.ammo_sec] then
mag_cell:Update( mags[size][mag_cell.grp][mag_cell.ammo_sec] or {})
--end
mags[size][mag_cell.grp][mag_cell.ammo_sec] = nil
end
for grp, ammo_list in pairs(mags[size]) do
for ammo_sec, list in pairs(ammo_list) do
mag_cell_list[#mag_cell_list+1] = UIMagCell(self.pouch[size],grp, list, size, group_by_ammo and ammo_sec or nil)
end
end
local start_x = 1
local cell_h = 10
local bkg = false
for _, mag_cell in ipairs(mag_cell_list) do
start_x = mag_cell:SetPos(start_x, self.mirrored)
cell_h = mag_cell.cell:GetHeight()
bkg = true
end
if bkg then
if self.mirrored then
self.bkg[size]:SetWndSize( vector2():set(-1*start_x+5, cell_h))
self.bkg[size]:SetWndPos(vector2():set(start_x, 0))
else
self.bkg[size]:SetWndSize( vector2():set(start_x+5, cell_h))
self.bkg[size]:SetWndPos(vector2():set(0, 0))
end
self.bkg[size]:Show(magazines_mcm.get_config("show_bgrd"))
end
end
if not (main_hud_shown()) then
self:Clear()
return
end
self.dialog:Show(magazines_mcm.get_config("show_hud"))
end
class "UIMagCell"
function UIMagCell:__init(owner, grp, list, size, ammo_sec)
self.owner = owner
self.grp = grp
self.count = #list
self.list = list
self.size = size
self.icos = {}
self.ammo_sec = ammo_sec or "none"
self.text = ammo_sec and magazines_mcm.get_config("icon_text") and ui_item.get_sec_short_name(ammo_sec) or ""
self.ammo_icon = self.ammo_sec ~= "none"
self.axis = utils_xml.get_item_axis(list[1] or "tch_mag_base")
self.small_ico = self.axis.h == 1
self.ammo_axis = self.ammo_icon and utils_xml.get_item_axis(self.ammo_sec)
self:InitControls()
end
function UIMagCell:InitControls()
self.cell = xml:InitStatic("mag", self.owner)
self.cell:SetWndSize( vector2():set((self.axis.w) * scale, (self.small_ico and 1 or 2)*50*scale + 5*scale))
self.cell:TextControl():SetText(self.text)
self.bkg = xml:InitStatic("mag", self.cell)
self.bkg:InitTexture( "ui_mags_bkg" )
--self.bkg:InitTexture( "ui\\ui_ingame2_common.dds" )
--self.bkg:SetTextureRect(Frect():set( 0, 285, 68, 324 ))
self.icos[1] = xml:InitStatic("mag", self.cell)
print_dbg("before icos setup")
self.icos[1]:SetWndSize( vector2():set((self.axis.w ) * scale,(self.axis.h ) * scale ))
self.icos[1]:InitTexture( utils_xml.get_icons_texture(self.list[1] or "tch_mag_base") )
self.icos[1]:SetTextureRect(Frect():set( utils_xml.get_item_axis(self.list[1] or "tch_mag_base", nil, true) ))
print_dbg("after icos setup")
self.ammo = xml:InitStatic("mini_ico", self.cell)
self.ammo:Show(self.ammo_icon and magazines_mcm.get_config("ammo_icon"))
if self.ammo_icon then
self.ammo:InitTexture( utils_xml.get_icons_texture(self.ammo_sec) )
self.ammo:SetTextureRect(Frect():set( utils_xml.get_item_axis(self.ammo_sec, nil, true) ))
self.ammo:SetWndSize( vector2():set((self.ammo_axis.w/2 )*ammo_scale,(self.ammo_axis.h/2 )*ammo_scale ))
end
adjust_vert(self.ammo)
scale_ui(self.cell)
scale_ui(self.bkg)
scale_ui(self.ammo)
scale_ui(self.icos[1])
self:Update(list)
end
function UIMagCell:Update(list)
self.list = list or {}
print_dbg("Update sec %s, c %s",count, self.grp)
self.count = #self.list
count = self.count
for i=1, count do
if not self.icos[i] then self.icos[i] = xml:InitStatic("mag", self.icos[i-1] ) end
local axis = utils_xml.get_item_axis(self.list[i])
self.icos[i]:SetWndSize( vector2():set((axis.w ) * scale, (axis.h ) * scale))
self.icos[i]:InitTexture( utils_xml.get_icons_texture(self.list[i]) )
self.icos[i]:SetTextureRect(Frect():set( utils_xml.get_item_axis(self.list[i], nil, true) ))
scale_ui(self.icos[i])
self.icos[i]:Show(true)
end
for i= count+1, #self.icos do
self.icos[i]:Show(false)
end
local width = self.icos[1]:GetWidth()
local offset = (width / 2)
local margin = width
if self.size == "small" then
offset = width/4
margin = width/2
elseif self.size == "medium" then
offset =(width / 3)
end
print_dbg("before cell width")
self.cell:SetWndSize( vector2():set((count*offset)+margin , (self.small_ico and 1 or 2)*50*scale + 5*scale))
print_dbg("before cell pos width:%s", width)
self.icos[1]:SetWndPos( vector2():set(self.cell:GetWidth()/2 - (count*offset+width/2)/2 , 0))
self.icos[1]:Show(count>0)
print_dbg("icos 1 count:%s show%s cell show %s",self.count,self.icos[1]:IsShown(), self.cell:IsShown() )
for i = 2, count do
self.icos[i]:SetWndPos(vector2():set(offset, 0))
self.icos[i]:Show(true)
end
self.bkg:SetWndSize( vector2():set(self.cell:GetWidth(), self.cell:GetHeight()))
self.bkg:Show(false)
--self.ammo:SetWndSize(
end
function UIMagCell:SetPos(x, mirrored)
if self.count == 0 then
self:Show(false)
print_dbg("count:%s hideing",self.count)
return x
end
local x_cord = x - (mirrored and self.cell:GetWidth() or 0)
self.cell:SetWndPos(vector2():set(x_cord, 0))
self:Show(true)
print_dbg("set pos count:%s show%s cell show %s x_cord %s owner show:%s",self.count,self.icos[1]:IsShown(), self.cell:IsShown(),x_cord,self.owner:IsShown() )
return x_cord + (mirrored and 0 or self.cell:GetWidth())
end
function UIMagCell:Show(show)
self.cell:Show(show)
end
----------------------------------
-- Callbacks
----------------------------------
local function on_option_change(mcm)
--if not mcm then return end
scale = magazines_mcm.get_config("scale")
ammo_scale = magazines_mcm.get_config("ammo_scale")
group_by_ammo = magazines_mcm.get_config("group_by_ammo")
grp_by_bt = magazines_mcm.get_config("grp_by_bt")
printf("sacle %s", scale)
deactivate_hud()
activate_hud()
end
local function actor_on_first_update()
scale = magazines_mcm.get_config("scale")
ammo_scale = magazines_mcm.get_config("ammo_scale")
group_by_ammo = magazines_mcm.get_config("scale")
grp_by_bt = magazines_mcm.get_config("ammo_scale")
deactivate_hud()
activate_hud()
end
local function actor_on_update()
HUD:Update()
end
function on_game_start()
RegisterScriptCallback("actor_on_first_update",actor_on_first_update)
RegisterScriptCallback("actor_on_update",actor_on_update)
RegisterScriptCallback("on_screen_resolution_changed",on_screen_resolution_changed)
RegisterScriptCallback("on_option_change",on_option_change)
end
function scale_ui(ele, adjust_x, anchor, anchor_point, parent_width)
p_width = parent_width or 1024
p_center = p_width/2
width = ele:GetWidth()
pos = ele:GetWndPos()
anchorpos = {}
anchorpos.left = pos.x
anchorpos.right = anchorpos.left + width
anchorpos.center = anchorpos.left + width/2
ratio = (device().height / device().width) / (768 / 1024)
xadjust = anchorpos.left
if adjust_x then
if anchor_point == "right" then
xadjust = p_width - (p_width - (anchor and anchorpos[anchor] or anchorpos.left))*ratio
elseif anchor_point == "center" then
xadjust = p_center - (p_center - (anchor and anchorpos[anchor] or anchorpos.left))*ratio
else
xadjust = ratio * (anchor and anchorpos[anchor] or anchorpos.left)
end
end
ele:SetWndSize(vector2():set(ele:GetWidth() * ratio, ele:GetHeight()))
ele:SetWndPos( vector2():set(xadjust , pos.y ) )
end
@@ -0,0 +1,741 @@
-- each datum consists of the following:
-- .loaded = this is a stack of rounds loaded in the magazine, each round is int, depending on what kind of round it is
-- .section = for magazines in weapons, this tracks the type of magazine that is loaded
local mags_storage = {}
-- this storage is for vested mags in specific
local carried_mags = {}
-- reverse lookups for magazine properties
local mags_by_basetype = {}
local mags_by_retool_group = {}
local basetypes_by_ammo_type ={}
local loadout_slots = {
small = 2,
medium = 0,
large = 0,
}
function print_dbg(...) magazines.print_dbg(...) end
function print_err(...) magazines.print_err(...) end
local function parent_section(sec)
return SYS_GetParam(0, sec, "parent_section", sec) or sec
end
function dump_data(data)
if not data or type(data) ~= "table" then
print_dbg("Mag data unavailable: %s (%s)", data, type(data))
else
local s = ""
for k,v in pairs(data.loaded) do
s = s .. v .. " "
end
print_dbg("Mag section: %s. Rounds loaded: %s. Rounds: %s", data.section, #data.loaded, s)
end
end
local look_up_table = ini_file_ex("magazines\\lookups.ltx")
local weapons_lookup = ini_file_ex("magazines\\weapons\\importer.ltx")
local loadout_lookup = ini_file_ex("magazines\\outfitloadouts\\importer.ltx")
-------------------------------
-- SECTION acess functions --
-------------------------------
function get_carried_mags(tbl)
--return carried_mags --efficency v acess control hud won't call this very often so went with acess control
copy_table(tbl, carried_mags )
end
function get_data(id)
tbl = {}
if mags_storage[id] then
copy_table(tbl, mags_storage[id] )
return tbl
end
return mags_storage[id] --could be nil or false want to return either.
end
function set_data(id, data)
mags_storage[id] = data
if carried_mags[id] then
carried_mags[id] = data
end
end
function create_mag_data(id, sec, is_weapon)
local mag_data = {}
mag_data.loaded = {}
mag_data.section = sec
mag_data.is_weapon = is_weapon or false
set_data(id, mag_data)
return mag_data
end
-- loops through mag data and check/validate entries
function clean_data()
for id, data in pairs(mags_storage) do
local se = alife_object(id)
local is_mag = se and SYS_GetParam(1, se:section_name(), "is_mag")
local is_wpn = se and is_supported_weapon(parent_section(se:section_name()))
if not (is_mag or is_wpn) then
set_data(id, nil)
elseif data == false then
set_data(id, {
section = "no_mag",
loaded = {},
is_weapon = true,
})
elseif is_wpn then
print_dbg("Checking wpn %s", se:section_name())
validate_wep(id, wpn_sec)
else
validate_mag(id, se:section_name())
end
end
end
function get_mag_property(id,key)
--print_dbg("wtf:".. tostring((mags_storage[id] and mags_storage[id][key] or "no dice")))
return mags_storage[id] and mags_storage[id][key]
end
function get_size(id, mag_data)
mag_data = mag_data or get_data(id)
return SYS_GetParam(0, mag_data.section, "mag_size") or "small"
end
function get_total_carried(exact)
local carried = {
["small"] = 0,
["medium"] = 0,
["large"] = 0
}
for id, mag in pairs(carried_mags) do
local size = get_size(id)
carried[size] = carried[size] + 1
end
if not exact then --shift exess up to next size
if carried.small > loadout_slots.small then
carried.medium = carried.medium + carried.small - loadout_slots.small
carried.small = loadout_slots.small
end
if carried.medium > loadout_slots.medium then
carried.large = carried.large + carried.medium - loadout_slots.medium
carried.medium = loadout_slots.medium
end
end
return carried
end
function get_loadout_size()
local copy = {}
copy_table(copy, loadout_slots)
return copy
end
function get_mags_for_basetype(basetype)
return mags_by_basetype[basetype] and dup_table( mags_by_basetype[basetype])
end
function get_basetypes_by_ammo_type(sec)
--print_dbg("#basetypes_by_ammo_type:%s basetypes_by_ammo_type[%s]:%s",#basetypes_by_ammo_type,sec,basetypes_by_ammo_type[sec] and #basetypes_by_ammo_type[sec])
return basetypes_by_ammo_type[sec] and dup_table( basetypes_by_ammo_type[sec])
end
function get_mags_by_ammo_type(sec)
local t = {}
local basetypes = get_basetypes_by_ammo_type(sec) or {}
for _,v in ipairs(basetypes) do
local mags = get_mags_for_basetype(v) or {}
for __,v2 in ipairs(mags) do
table.insert(t, v2)
end
end
return t
end
-------------------------------
-- SECTION utility functions --
-------------------------------
local function type_correction(val)
if not val then return end
-- print_dbg(type(val))
local id, section, obj, se_obj
if type(val) == "string" then
section = val
elseif type(val) == "number" then
id = val
obj = level.object_by_id(id)
se_obj = alife_object(id)
if obj then
section = obj:section()
elseif se_obj then
section = se_obj:section_name()
else
print_dbg("WTF is:%s any way?", id)
end
elseif type(val.id) == "number" then
id = val.id
se_obj = val
obj = level.object_by_id(id)
section = val:section_name()
elseif type(val.id) == "function" then
id = val:id()
obj = val
se_obj = alife_object(id)
section = val:section()
end
return id, section, obj, se_obj
end
function is_carried_mag(id)
return carried_mags[id] and true or false
end
function toggle_carried_mag(id)
-- print_dbg("tcm1")
if carried_mags[id] then
carried_mags[id] = nil
return false
--print_dbg("tcm2")
elseif room_in_pouch(id) then
carried_mags[id] = mags_storage[id]
return true
--print_dbg("tcm3"..tostring(carried_mags[id] and carried_mags[id].section))
end
end
function update_loadout_slots()
local outfit = db.actor:item_in_slot(7)
local small,medium,large = 0,0,0
if outfit then
small,medium,large = get_loadout_slots(outfit)
--print_dbg("Outfit:%s||S:%s|M:%s|L:%s",outfit:section(), small,medium,large)
else
small,medium,large = get_loadout_slots("o_none")
--print_dbg("Outfit:%s||S:%s|M:%s|L:%s","o_none", small,medium,large)
end
local s,m,l = 0,0,0
local backpack = db.actor:item_in_slot(13)
s,m,l = get_loadout_slots(backpack)
--print_dbg("backpack:%s||S:%s|M:%s|L:%s",backpack and backpack:section(), s,m,l)
small = small + s
medium = medium + m
large = large + l
--print_dbg("Outfit+backpack||S:%s|M:%s|L:%s", small,medium,large)
local ss,mm,ll = 0,0,0
db.actor:iterate_belt( function(owner, obj)
s,m,l = get_loadout_slots(obj)
--print_dbg("belt:%s||S:%s|M:%s|L:%s",obj and obj:section(), s,m,l)
ss = ss + s
mm = mm + m
ll = ll + l
end)
--print_dbg("Belt total||S:%s|M:%s|L:%s", ss,mm,ll)
small = small + ss
medium = medium + mm
large = large + ll
--print_dbg("total slots||S:%s|M:%s|L:%s", small,medium,large)
loadout_slots.small = small
loadout_slots.medium = medium
loadout_slots.large = large
end
function validate_loadout()
update_loadout_slots()
local carried = get_total_carried()
local excess = carried.large - loadout_slots.large
if excess < 1 then -- if they shift arround and don't over fill large all good
--print_dbg("validate_loadout s:%s m:%s l:%s excess:%s", carried.small, carried.medium, carried.large, excess)
return
end
local found = {small = 0, medium = 0, large = 0}
for id, mag in pairs(carried_mags) do --shed exess favoring shifted mags.
local size = get_size(id)
if size then
found[size] = found[size] + 1
if found[size] > loadout_slots[size] then
carried_mags[id] = nil
excess = excess - 1
if excess == 0 then break end
end
end
end
magazines.inventory_refresh()
end
function room_in_pouch(id)
local s = mags_storage[id] and get_size(id) or false
if not s then return false end
for i, mag in pairs(carried_mags) do -- safety purge of escaped mags
local itm = level.object_by_id(i)
if not (itm and utils_item.in_actor_inv(itm)) then
carried_mags[i] = nil
end
end
carried = get_total_carried()
return carried.large < loadout_slots.large or ((s == "medium" or s == "small") and (carried.medium < loadout_slots.medium)) or (s == "small" and (carried.small < loadout_slots.small))
end
function build_mag_revers_lookups()
local base_types = {}
local function itr(section)
if not is_magazine(section) or section == "tch_mag_base" or SYS_GetParam(1, section, "old_mag", false) then return end
local basetype = SYS_GetParam(0, section, "base_type") or nil
local retool_group = SYS_GetParam(0, section, "retool_group") or nil
local caliber = get_magazine_caliber(section)
if basetype then
if not mags_by_basetype[basetype] then
mags_by_basetype[basetype] = {}
end
mags_by_basetype[basetype][#mags_by_basetype[basetype]+1] = section
end
if retool_group then
if not mags_by_retool_group[retool_group] then
mags_by_retool_group[retool_group] = {}
end
mags_by_retool_group[retool_group][#mags_by_retool_group[retool_group]+1] = section
end
if caliber and basetype and not base_types[basetype] then
base_types[basetype] = true
for i = 1, #caliber do
if not basetypes_by_ammo_type[caliber[i]] then
basetypes_by_ammo_type[caliber[i]] = {}
end
basetypes_by_ammo_type[caliber[i]][#basetypes_by_ammo_type[caliber[i]]+1] = basetype
end
end
end
ini_sys:section_for_each(itr)
end
-- these functions work equally well given an object id, item section, gameobject or server object.
function is_supported_weapon(val)
if not val then return end
local id, section, obj, se_obj = type_correction(val)
local is_valid_by_sec = weapons_lookup:section_exist(parent_section(section))
--print_dbg("is_valid_by_sec:%s and obj: %s", is_valid_by_sec, obj and true)
if (not (obj and is_valid_by_sec)) or obj:weapon_in_grenade_mode() then return is_valid_by_sec end
local is_valid = is_valid_by_sec and get_weapon_base_type(id) and true or false
--print_dbg("is_valid:%s and obj: %s", is_valid, obj and true)
-- if it's a valid weapon, bind it to the mag binder
-- HarukaSai: we don't need a binder just for first update, instead we can create data here
if is_valid then
local mag_data = get_data(id)
if not mag_data then
print_dbg("Valid weapon %s has no data, creating default data", obj:section())
local default_mag = weapon_default_magazine(section)
mag_data = create_mag_data(id, default_mag, true)
-- also need to do some things to convert existing ammo
local ammo_max = SYS_GetParam(2, default_mag, "max_mag_size")
local ammo_type = obj:get_ammo_type()
local ammo_map = utils_item.get_ammo(section, id) or SYS_GetParam(2, section, "ammo_mag_size") or 999
print_dbg("Weapon %s uses mags, assigning default mag %s with %s rounds, type is %s", section, default_mag, ammo_max, ammo_map[ammo_type+1])
for i=1,ammo_max do
stack.push(mag_data.loaded, ammo_map[ammo_type+1])
end
set_data(id, mag_data)
end
end
return is_valid
end
function is_open_bolt_weapon(val)
if not val then return end
local id, section, obj, se_obj = type_correction(val)
return look_up_table:r_value(parent_section(section), "open_bolt")
end
function has_loadout_slots(val)
if not val then return end
local id, section, obj, se_obj = type_correction(val)
local in_list = loadout_lookup:section_exist(parent_section(section))
--print_dbg("has_loadout_slots %s|%s:%s|%s", section,parent_section(section), in_list, IsItem("outfit", section, obj) )
return IsItem("outfit", section, obj) or in_list
end
function get_retool_section(val)
if not val then return end
local id, section, obj, se_obj = type_correction(val)
local retool_group = SYS_GetParam(0, section, "retool_group") or nil
local retool_section = nil
if retool_group and mags_by_retool_group[retool_group] and #mags_by_retool_group[retool_group] > 1 then --want to return nil of mag has no retool group or is only memeber
for i,v in ipairs(mags_by_retool_group[retool_group]) do
print_dbg("retool section: %s|%s", section, v)
if v == section then
if i < #mags_by_retool_group[retool_group] then
retool_section = mags_by_retool_group[retool_group][i+1]
else
retool_section = mags_by_retool_group[retool_group][1]
end
break
end
end
end
return retool_section
end
function get_loadout_slots(val, combine, force_outfit)
if not val then return 0,0,0 end
local id, section, obj, se_obj = type_correction(val)
local s,m,l = 0,0,0
if look_up_table:section_exist(section) then
s = look_up_table:r_value(section, "mag_limit_small", 2) or s
m = look_up_table:r_value(section, "mag_limit_medium", 2) or m
l = look_up_table:r_value(section, "mag_limit_large", 2) or l
elseif IsOutfit(obj) or force_outfit then
local kind = SYS_GetParam(0, section, "kind")
if look_up_table:section_exist(kind) then
s = look_up_table:r_value(kind, "mag_limit_small", 2) or s
m = look_up_table:r_value(kind, "mag_limit_medium", 2) or m
l = look_up_table:r_value(kind, "mag_limit_large", 2) or l
else
print_dbg("Outfit defaulted: sec:%s kind:%s",section ,kind )
s = look_up_table:r_value("o_none", "mag_limit_small", 2) or s
m = look_up_table:r_value("o_none", "mag_limit_medium", 2) or m
l = look_up_table:r_value("o_none", "mag_limit_large", 2) or l
end
end
if combine then
return {small = s,medium = m, large = l}
else
return s, m, l
end
end
function is_magazine(val)
if not val then return false end
local id, section, obj, se_obj = type_correction(val)
return SYS_GetParam(1, section, "is_mag")
end
function get_magazine_base_type(val)
if not val then return end
local id, section, obj, se_obj = type_correction(val)
return SYS_GetParam(0, section, "base_type") or nil
end
function get_magazine_caliber(val)
return str_explode(look_up_table:r_value(get_magazine_base_type(val) or print_dbg("Missing basetype for: %s",val), "caliber") or print_dbg("Invalid basetyper for: %s",val), ",")
end
-- check if this weapon takes a magazine, and return the base type. false if it does not
function get_weapon_base_type(val)
if not val then return end
local id, section, obj, se_obj = type_correction(val)
id = obj and id or nil --utils_item.get_ammo will not work if given an id of an offline object. nil id if no gameobject exists
local ammo = utils_item.get_ammo(section, id)[1]
local parent = parent_section(section)
print_dbg("for weapon %s, using ammo %s, bt is %s", parent, ammo, is_supported_weapon(section) and look_up_table:r_value(parent, ammo))
return is_supported_weapon(section) and look_up_table:r_value(parent, ammo) -- base_type is in the ltx based on the first entry in the weapons ammo list.
end
function weapon_default_magazine(val)
if not val then return end
local id, section, obj, se_obj = type_correction(val)
return look_up_table:r_value(parent_section(section), "default_mag")
end
function weapon_improved_magazine(val)
if not val then return end
local id, section, obj, se_obj = type_correction(val)
return look_up_table:r_value(parent_section(section), "improved_mag")
end
-- check if mag is compatible w. weapon
function is_compatible(weapon, magazine) --both called functions do type correction, added argument order corection
if not is_magazine(magazine) then
local t = magazine
magazine = weapon
weapon = t
end
local weapon_base = get_weapon_base_type(weapon)
local magazine_base = get_magazine_base_type(magazine)
print_dbg("wpn base type is %s, mag base type is %s", weapon_base, magazine_base)
return weapon_base == magazine_base
end
function valid_mag_data(mag_data)
return (mag_data and mag_data.section ~= "no_mag") and mag_data or nil
end
-- get mag data with validation for fake mag items
function get_mag_loaded(id)
return valid_mag_data(get_data(id))
end
function validate_wep(id, sec)
local wpn_obj = level.object_by_id(id)
local mag_data = get_mag_loaded(id)
if not mag_data then return end
if wpn_obj and wpn_obj:weapon_in_grenade_mode() then return end
local w_bt = get_weapon_base_type(id)
local m_bt = get_magazine_base_type(mag_data.section)
if w_bt ~= m_bt then
print_err("(CLEANUP) Incompatible magazine %s (bt %s) found on gun %s (typ %s, bt %s). Granting default magazine.", mag_data.section, m_bt, id, sec, w_bt)
mag_data.section = weapon_default_magazine(wpn_obj)
empty_table(mag_data.loaded)
set_data(id, mag_data)
end
end
function validate_mag(id, sec)
local mag_data = get_mag_loaded(id)
if not mag_data then return end
if sec ~= mag_data.section then
print_err("(CLEANUP) Mag section mismatch! Saved type is %s, actual %s for id %s", mag_data.section, sec, id)
mag_data.section = sec
end
-- check for ammo that shouldn't belong in the magazine, and replace with generic ammo
local ammo_map = invert_table(get_magazine_caliber(mag_data.section))
-- remove excess rounds
local ammo_cap = SYS_GetParam(2, mag_data.section, "max_mag_size")
while #mag_data.loaded > ammo_cap do
print_err("(CLEANUP) Loaded magazine %s (%s) has capacity %s, current %s rounds loaded", mag_data.section, id, ammo_cap, #mag_data.loaded)
stack.pop(mag_data.loaded)
end
local ammo_replace = random_key_table(ammo_map)
for k,v in pairs(mag_data.loaded) do
if not ammo_map[v] then
print_err("(CLEANUP) Invalid round %s loaded in mag %s, id %s, replacing", v, mag_data.section, id)
mag_data.loaded[k] = ammo_replace
end
end
set_data(id, mag_data)
end
-- class
function bind(obj)
obj:bind_object(magazine_binder(obj))
end
class "magazine_binder" (object_binder)
function magazine_binder:__init(obj) super(obj)
self.first_update = nil
end
-- global flag used to cap condition at 100 for trade
local freeze = false
-- only update every half second
local update_tick = 500
function magazine_binder:update(delta)
object_binder.update(self, delta)
local tg = time_global()
local obj = self.object
local id = obj:id()
local sec = obj:section()
local mag_data = get_data(id)
if not self.first_update then
self.first_update = true
self.last_update = tg + update_tick
-- associate mag data to empty magazine object
if not mag_data and is_magazine(obj) then
mag_data = create_mag_data(id, sec, false)
end
end
if tg < self.last_update then return end
self.last_update = tg + update_tick
-- update mag weight and cond
if is_magazine(obj) then
if freeze then
obj:set_condition(0.999)
else
local mag_weight = SYS_GetParam(2, sec, "inv_weight")
local capacity = SYS_GetParam(2, sec, "max_mag_size")
-- for simplicity we take the weight of each bullet to be the same
local cond = 0
if mag_data and #mag_data.loaded > 0 then
local ammoType = get_magazine_caliber(sec)[1]
local box_size = SYS_GetParam(2, ammoType, "box_size") or 1
local box_weight = SYS_GetParam(2, ammoType, "inv_weight") or 0
local cartridge_weight = box_weight / box_size
cond = #mag_data.loaded / capacity
mag_weight = mag_weight + (#mag_data.loaded * cartridge_weight)
end
set_data(id, mag_data)
obj:set_weight(mag_weight)
obj:set_condition(cond)
end
end
end
function magazine_binder:reload(section)
object_binder.reload(self, section)
end
function magazine_binder:reinit()
object_binder.reinit(self)
end
function magazine_binder:net_spawn(se_abstract)
if not(object_binder.net_spawn(self, se_abstract)) then
return false
end
return true
end
function magazine_binder:net_destroy()
object_binder.net_destroy(self)
end
function magazine_binder:save(stpk)
end
function magazine_binder:load(stpk)
end
-- end class
-------------------------------
-- SECTION inventory highlight --
-------------------------------
-- this gets called _ALOT_ so putting it here where table can be read
local bags = {actor_bag = true,actor_trade_bag= true} --player inv in normal/looting and in the merchant UI.
ready_color = GetARGB(100, 255, 159, 82)
function check_ready(cell)
if bags[cell.container.ID] then
return carried_mags[cell.ID] and ready_color --cell.ID and cell.sec are the object id and section
end
end
function icon_check_ready(cell)
if bags[cell.container.ID] and carried_mags[cell.ID] then
return {texture = "ui_mags_loadout", x = 1, y = 1, w = 15, h = 15}
end
end
-------------------------------
-- SECTION callbacks --
-------------------------------
local function save_state(mdata)
mdata.mags_storage = mags_storage
mdata.carried_mags = carried_mags
end
function load_state(mdata)
mags_storage = mdata.mags_storage or {}
carried_mags = mdata.carried_mags or {}
end
-- attempt to keep mstorage clean
local function on_register(se_obj, typ)
local id = se_obj.id
local sec = se_obj:section_name()
if mags_storage[id] and not (is_magazine(sec) or is_supported_weapon(sec)) then
mags_storage[id] = nil
end
end
local function se_item_on_unregister(se_obj, typ)
local id = se_obj.id
mags_storage[id] = nil
carried_mags[id] = nil
end
local past_first_update = false
function actor_on_first_update()
CreateTimeEvent("mag_binder","firstupdatedelay",1,function()
past_first_update = true
validate_loadout()
clean_data()
return true
end)
end
function actor_item_to_slot(obj)
if past_first_update and has_loadout_slots(obj) then
validate_loadout()
end
end
function on_trade_opened()
freeze = true
end
function on_trade_closed()
freeze = false
end
function on_game_start()
build_mag_revers_lookups()
RegisterScriptCallback("save_state",save_state)
RegisterScriptCallback("load_state",load_state)
rax_persistent_highlight.register("ready_mag", check_ready) --used like a callback register
rax_icon_layers.register("ready_mag", icon_check_ready) --used like a callback register
RegisterScriptCallback("actor_item_to_slot",actor_item_to_slot)
RegisterScriptCallback("actor_item_to_ruck",actor_item_to_slot)
RegisterScriptCallback("actor_item_to_belt",actor_item_to_slot)
RegisterScriptCallback("actor_on_item_drop",actor_item_to_slot)
RegisterScriptCallback("server_entity_on_unregister",se_item_on_unregister)
RegisterScriptCallback("actor_on_first_update", actor_on_first_update)
RegisterScriptCallback("ActorMenu_on_trade_started",on_trade_opened)
RegisterScriptCallback("ActorMenu_on_trade_closed",on_trade_closed)
end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,23 @@
-- hotkeys and stuff
print_dbg = magazines.print_dbg
-- shift click to unload
InventoryClick = ui_inventory.UIInventory.On_CC_Mouse1
function ui_inventory.UIInventory:On_CC_Mouse1(bag, idx)
InventoryClick(self, bag, idx)
local obj = self.CC[bag]:GetObj(idx)
if (not obj) then
self:Print(nil, "Callback On_CC_Mouse1 | no object recieved!", bag, idx)
return
end
if ((key_state(DIK_keys.DIK_RSHIFT) or 0) ~= 0 or (key_state(DIK_keys.DIK_LSHIFT) or 0) ~= 0) and (bag == "actor_bag" or bag == "npc_bag")then
if magazine_binder.is_magazine(obj) then
magazines.func_unload_ammo(obj)
print_dbg("shift unload")
elseif magazine_binder.is_supported_weapon(obj) then
magazines.eject_magazine(obj)
end
end
end
@@ -0,0 +1,236 @@
----------------------------------------------------------------
-- Deal with loot injection in stalkers and traders
----------------------------------------------------------------
gc = game.translate_string
get_data = magazine_binder.get_data
set_data = magazine_binder.set_data
get_mag_loaded = magazine_binder.get_mag_loaded
prep_weapon = magazines.prep_weapon
is_supported_weapon = magazine_binder.is_supported_weapon
get_magazine_caliber = magazine_binder.get_magazine_caliber
is_magazine = magazine_binder.is_magazine
weapon_default_magazine = magazine_binder.weapon_default_magazine
weapon_improved_magazine = magazine_binder.weapon_improved_magazine
create_mag_data = magazine_binder.create_mag_data
print_dbg = magazines.print_dbg
get_weapon_base_type = magazine_binder.get_weapon_base_type
get_mags_for_basetype = magazine_binder.get_mags_for_basetype
valid_mag_data = magazine_binder.valid_mag_data
get_config = magazines_mcm.get_config
validate_mag = magazine_binder.validate_mag
validate_wep = magazine_binder.validate_wep
local math_random = math.random
local math_floor = math.floor
local string_format = string.format
local print_table = utils_data.print_table
local ini_loadouts = ini_file("items\\settings\\npc_mag_loadouts.ltx")
local mag_timer_global = nil
local tm = {}
-- trader management
TraderAuto = trader_autoinject.update
function trader_autoinject.update(npc)
TraderAuto(npc)
CreateTimeEvent("restock_mags" .. npc:id(), "restock_mags" .. npc:id(), 0.01, function()
stock_mags(npc)
return true
end)
end
-- function to resupply mags based on what weapons are in stock
-- formula is: 3 mags for 1st weapon, and 1 extra for each subsequent
function stock_mags(npc)
local id = npc:id()
if trader_autoinject.get_trader_type(npc) ~= trader_autoinject.SUPPLIER then return end
print_dbg("Restocking mags for %s", npc:name())
local to_spawn = {}
-- collect num. of mags to spawn
local function itr_inv(temp, item)
local sec = item:section()
if not IsAmmo(item) and IsWeapon(item) and is_supported_weapon(item) then
local default_mag = weapon_default_magazine(item)
local default_capacity = SYS_GetParam(2, default_mag, "max_mag_size")
local default_load_delay = SYS_GetParam(2, default_mag, "load_delay")
local mags = get_mags_for_basetype(get_weapon_base_type(item))
if mags then
print_dbg("Mags [%s]", sec)
for _, mag in pairs(mags) do
local load_delay = SYS_GetParam(2, mag, "load_delay")
local capacity = SYS_GetParam(2, mag, "max_mag_size")
if mag ~= default_mag and (capacity > default_capacity or load_delay < default_load_delay) then
print_dbg("Mag %s is improved", mag)
to_spawn[mag] = to_spawn[mag] and to_spawn[mag] + math_random(0, 1) or math_random(1, 2)
else
print_dbg("Mag %s is normal", mag)
to_spawn[mag] = to_spawn[mag] and to_spawn[mag] + 1 or 3
end
end
else
print_dbg("Weapon has broken basetype [%s]", sec)
end
end
end
npc:iterate_inventory(itr_inv, npc)
-- spawn them empty
trader_autoinject.spawn_items(npc, to_spawn, true)
end
-- death management
local function get_mag_prop(rank, prop)
rank = rank or "novice"
return ini_loadouts:r_float_ex(rank.."_mag_loadout", prop)
end
-- called on each created magazine, autofill with the appropriate crap
function random_pop_mag(mag_id, mag_sec, ammo_table, rank)
local quality = get_config("deathquality") or 1
local amount = math_random(0, get_mag_prop(rank, "mag_fill_max"))
amount = clamp(amount * (get_config("deathammo") or 1), 0, 100)/100
local mag_data = get_mag_loaded(mag_id) or create_mag_data(mag_id, mag_sec)
empty_table(mag_data.loaded)
local to_fill = math_floor(amount * SYS_GetParam(2, mag_sec, "max_mag_size"))
-- also pick the appropriate ammo
local good_chance = get_mag_prop(rank, "mag_good_chance") * quality
-- add validation for existence of a single ammo type (e.g. 9x21 sp10)
-- This should really be refactored to categorize bad ammo in the ammo table instead of relying on the index
local ammo_to_pick = math_random(#ammo_table)
if #ammo_table > 1 then
ammo_to_pick = 3*math_floor(math_random(#ammo_table - 1) / 3) + 1 + (math_random(100) < good_chance and 0 or 1)
else
to_fill = math_floor(to_fill/2)
end
local ammo_to_use = determine_ammo(ammo_table[ammo_to_pick], mag_sec, rank)
print_dbg("Filling mag %s to %s with %s (num %s)", mag_sec, to_fill, ammo_to_use, ammo_to_pick)
for i=1,to_fill do
stack.push(mag_data.loaded, ammo_to_use)
end
set_data(mag_id, mag_data)
return true
end
function determine_ammo(ammo_to_pick, mag_sec, rank)
return ammo_to_pick
end
function npc_on_death(npc, who)
local rank = ranks.get_obj_rank_name(npc)
local found_primary = false
local found_secondary = false
function itr_inv(temp, item)
local sec = item:section()
if IsWeapon(nil,item:clsid()) and not npc:marked_dropped(item) and is_supported_weapon(item) then
-- spawn mags for one primary, one secondary
local is_sidearm = IsPistol(item)
if (found_primary and not is_sidearm) or (found_secondary and is_sidearm) then return end
-- reduce ammo in loaded weapon
local id = item:id()
local mag_data = get_data(id)
local mags_to_spawn = math_random(0, get_mag_prop(rank, "max_mags"))
local mag_sec = weapon_default_magazine(sec)
local improved_mag_sec = weapon_improved_magazine(sec)
local mag_good_chance = get_mag_prop(rank, "mag_good_chance")
local ammo_table = get_magazine_caliber(mag_sec)
local quality = get_config("deathquality") or 1
print_dbg("Spawning %s mags for %s", mags_to_spawn, sec)
for i=1,mags_to_spawn do
local to_create = math_floor(quality * math_random(100)) <= mag_good_chance and improved_mag_sec or mag_sec
print_dbg("Creating magazine %s", to_create)
local new_mag = alife_create_item(to_create, npc)
if new_mag then
random_pop_mag(new_mag.id, to_create, ammo_table, rank)
end
end
if is_sidearm then
found_secondary = true
else
found_primary = true
end
end
end
npc:iterate_inventory(itr_inv, npc)
end
SetWepCondition = death_manager.set_weapon_drop_condition
function death_manager.set_weapon_drop_condition(npc, itm)
SetWepCondition(npc, itm)
local death_dropped = se_load_var(npc:id(), npc:name(), "death_dropped")
if (death_dropped) then
return
end
local id = itm:id()
if not is_supported_weapon(itm) then
set_data(id, nil)
return
end
if (is_supported_weapon(itm) and not npc:marked_dropped(itm)) then
local id = itm:id()
local sec = itm:section()
local mag_sec = weapon_default_magazine(sec)
local ammo_table = get_magazine_caliber(mag_sec)
local rank = ranks.get_obj_rank_name(npc)
print_dbg("Resetting mag data for weapon %s (%s)", id, itm:section())
itm:unload_magazine()
random_pop_mag(id, mag_sec, ammo_table, rank)
prep_weapon(itm, false)
end
end
-- 1.5.2 feature - determine mag cost based on contents
function on_get_item_cost(kind, obj, profile, calculated_cost, ret)
local id = obj:id()
local sec = obj:section()
local mag_data = get_mag_loaded(id)
local calculated_cost = ret.new_cost or calculated_cost -- in case someone adjusted the price of the weapon
if mag_data == nil then return end
-- we have mag data - but it can potentially be corrupt. perform validation if needed
-- print_dbg("Item %s has mag data", sec)
if is_magazine(obj) then
validate_mag(id, sec)
local cost_base = obj:cost()
-- non stalkers, reduce mag cost
if profile.mode == 1 then
cost_base = cost_base * 0.1
end
local info = mags_patches.collect_mag_data(mag_data)
for k,v in pairs(info) do
local cost_frac = math_floor(SYS_GetParam(2, k, "cost") / SYS_GetParam(2, k, "box_size")) * v
-- print_dbg("Mag %s, cost %s, add cost %s from %s of %s", sec, cost_base, cost_frac, v, k)
cost_base = cost_base + cost_frac
end
ret.new_cost = cost_base * profile.discount
print_dbg("Final cost of %s is %s, discount %s", sec, ret.new_cost, profile.discount)
elseif is_supported_weapon(obj) and mag_data then
validate_wep(id, sec)
local mag_cost = SYS_GetParam(2, mag_data.section, "cost") * profile.discount
if profile.mode == 1 then
mag_cost = mag_cost * 0.1
end
-- print_dbg("Weapon %s, adding mag %s cost %s", sec, mag_data.section, mag_cost)
ret.new_cost = calculated_cost + mag_cost
else
printf("!!! FRAUD DETECTED !!! Item %s should not have mag data!", sec)
set_data(id, nil)
end
end
function on_game_start()
if utils_item.on_get_item_cost then
RegisterScriptCallback("on_get_item_cost", on_get_item_cost)
end
RegisterScriptCallback("npc_on_death_callback", npc_on_death)
end
@@ -0,0 +1,132 @@
-- If you don't use MCM, change your defaults from here.
local defaults = {
debug = false,
mag_loadtime_factor = 1,
mag_unloadtime_factor = 1,
mag_tooltip = 1,
empty_mags_stack = true,
sort_inv = true,
show_hud = true,
icon_text = true,
ammo_icon = true,
scale = 1,
ammo_scale = 1,
group_by_ammo = true,
grp_by_bt = true,
show_bgrd = true,
bgrd = 1,
load_behavior = 0,
unload_behavior = 0,
loadstash_behavior = 0,
retain_round = false,
ejection = 0,
tacload = false,
mag_unequip_trs = 1,
three_hands = false,
deathquality = 1.0,
deathammo = 1.0,
}
local def_map = {
debug = "mgmt",
mag_loadtime_factor = "mgmt",
mag_unloadtime_factor = "mgmt",
mag_tooltip = "mgmt",
empty_mags_stack = "mgmt",
sort_inv = "mgmt",
show_hud = "hud",
icon_text = "hud",
ammo_icon = "hud",
scale = "hud",
ammo_scale = "hud",
group_by_ammo = "hud",
grp_by_bt = "hud",
show_bgrd = "hud",
bgrd = "hud",
load_behavior = "gameplay",
unload_behavior = "gameplay",
loadstash_behavior = "gameplay",
retain_round = "gameplay",
ejection = "gameplay",
tacload = "gameplay",
mag_unequip_trs = "gameplay",
three_hands = "gameplay",
deathquality = "gameplay",
deathammo = "gameplay",
}
local colors = {--alpha value will be reset to match slider
orange = GetARGB(100, 255, 159, 82),
pink = GetARGB(100, 255, 192, 203),
blond = GetARGB(100, 240, 240, 190),
white = GetARGB(100, 255, 255, 255),
brown = GetARGB(100, 165, 42, 42),
blue = GetARGB(100, 0, 0, 255),
}
function get_config(key)
if ui_mcm then return ui_mcm.get("magazines/"..def_map[key].."/"..key) else return defaults[key] end
end
function on_mcm_load()
op = { id= "magazines",gr={
{id = "mgmt",sh=true,gr={
{id= "mgmtitle",type= "slide",text="ui_mcm_magazines_mgmtitle",link= "ui_options_slider_player",size= {512,50},spacing= 20},
{id = "debug", type = "check", val = 1, def=false},
{id = "mag_loadtime_factor", type = "track", val = 2, min=0.4,max=2,step=0.1, def = 1},
{id = "mag_unloadtime_factor", type = "track", val = 2, min=0.4,max=2,step=0.1, def = 1},
{id= "mag_tooltip" ,type= "list", val= 2 ,def= 1 ,content= {{0,"topround"}, {1,"trcap"}, {2,"full"}} },
{id = "empty_mags_stack", type = "check", val = 1, def=true},
{id = "sort_inv", type = "check", val = 1, def=true},
{id= "highlight_color" ,type= "list" ,val= 0 ,def= "orange" ,content= {{"brown","ram_brown"},{"white","ram_white"}, {"blond","ram_blond"} , {"blue","ram_blue"}, {"orange","ram_orange"} , {"pink","ram_pink"} } },
{id= "highlight_clr_a" ,type= "track" ,val= 2 ,min= 0 ,max= 255 ,step= 1 ,def= 100 },
}},
{id = "hud",sh=true,gr={
{id= "hudtitle",type= "slide",text="ui_mcm_magazines_hudtitle",link= "ui_options_slider_player",size= {512,50},spacing= 20},
{id = "show_hud", type = "check", val = 1, def=true},
{id = "scale", type = "track", val = 2, min=0.1,max=2,step=0.05, def = 1},
{id = "grp_by_bt", type = "check", val = 1, def=true},
{id = "group_by_ammo", type = "check", val = 1, def=true},
{id = "icon_text", type = "check", val = 1, def=true},
{id = "ammo_icon", type = "check", val = 1, def=true},
{id = "ammo_scale", type = "track", val = 2, min=0.1,max=2,step=0.05, def = 1},
{id = "show_bgrd", type = "check", val = 1, def=true},
{id = "bgrd" ,type= "list" ,val= 2, def= 1, content= {{1,1},{2,2},{3,3},{4,4},{5,5},{6,6},{7,7},{8,8},{9,9},{10,10},{11,11},{12,12}},no_str = true},
}},
{id = "gameplay",sh=true,gr={
{id= "gametitle",type= "slide",text="ui_mcm_magazines_gametitle",link= "ui_options_slider_player",size= {512,50},spacing= 20},
{id = "deathquality", type = "track", val = 2, min=0.5,max=2,step=0.1, def = 1},
{id = "deathammo", type = "track", val = 2, min=0.5,max=2,step=0.1, def = 1},
{id = "load_behavior" ,type= "list" ,val= 2, def= 0, content = {{0,"ind"},{2,"box"}, {1, "full"}}},
{id = "unload_behavior" ,type= "list" ,val= 2, def= 0, content={{0,"ind"}, {1, "full"}}},
{id = "loadstash_behavior" ,type= "list" ,val= 2, def= 1, content= {{0,"ind"},{2,"box"}, {1, "full"}}},
{id = "retain_round", type = "check", val = 1, def=false},
{id = "mag_unequip_trs", type = "track", val = 2, min=0,max=100,step=1, def = 1},
{id = "ejection" ,type= "list" ,val= 2, def= 1, content= {{0,"loadout"},{1,"ruck"}}},
{ id= "rant", type= "desc", text= "ui_mcm_magazines_rant", clr= {255, 225, 0, 0}},
{id = "three_hands", type = "check", val = 1, def=false},
}},
}
}
return op
end
local function on_option_change()
if ui_mcm then
magazine_binder.ready_color = rax_persistent_highlight.change_alpha(colors[ui_mcm.get("magazines/mgmt/highlight_color")] or colors.orange , ui_mcm.get("magazines/mgmt/highlight_clr_a") or 100 )
empty_table(magazines.cache_mag_time)
end
end
function on_game_start()
RegisterScriptCallback("on_option_change",on_option_change)
on_option_change()
end
@@ -0,0 +1,869 @@
gc = game.translate_string
get_data = magazine_binder.get_data
set_data = magazine_binder.set_data
dump_data = magazine_binder.dump_data
is_supported_weapon = magazine_binder.is_supported_weapon
get_magazine_caliber = magazine_binder.get_magazine_caliber
is_compatible = magazine_binder.is_compatible
is_magazine = magazine_binder.is_magazine
get_weapon_base_type = magazine_binder.get_weapon_base_type
is_carried_mag = magazine_binder.is_carried_mag
toggle_carried_mag = magazine_binder.toggle_carried_mag
room_in_pouch = magazine_binder.room_in_pouch
eject = magazines.eject_magazine
get_total_carried = magazine_binder.get_total_carried
get_loadout_size = magazine_binder.get_loadout_size
weapon_default_magazine = magazine_binder.weapon_default_magazine
get_mag_loaded = magazine_binder.get_mag_loaded
print_dbg = magazines.print_dbg
has_loadout_slots = magazine_binder.has_loadout_slots
get_loadout_slots = magazine_binder.get_loadout_slots
create_mag_data = magazine_binder.create_mag_data
validate_mag = magazine_binder.validate_mag
validate_wep = magazine_binder.validate_wep
create_time_event = magazines.create_time_event
get_sec_chambered = magazines.get_sec_chambered
get_config = magazines_mcm.get_config
function stack_rule(obj)
local sec = obj:section()
local mag_data = get_data(obj:id())
--print_dbg("stack rule sec:%s id:%s loaded%s config%s", sec, obj:id(), (mag_data and #mag_data.loaded == 0), magazines_mcm.get_config("empty_mags_stack"))
return (( not is_carried_mag(obj:id())) and ((mag_data and #mag_data.loaded == 0) and magazines_mcm.get_config("empty_mags_stack")) )
end
rax_stacking_control.register(is_magazine, stack_rule)
mousebase = utils_ui.UICellContainer.On_Mouse1
local d_flag = false
function utils_ui.UICellContainer:On_Mouse1(idx)
mousebase(self, idx)
if d_flag then
create_time_event("mag_redux", "delay_toggle", 0,
function(id)
if is_magazine(id) and toggle_carried_mag(id) ~= nil then
magazines.inventory_refresh()
end
return true
end, self.cell[idx].ID)
end
end
function ui_inventory.UIInventory:Cond_Unload(obj, bag)
obj = self:CheckItem(obj,"Cond_Unload")
local sec = obj:section()
local mag_data = get_mag_loaded(obj:id())
if IsWeapon(obj) and (obj:weapon_in_grenade_mode() or not mag_data) and (not IsItem("fake_ammo_wpn",sec)) then
return obj:get_ammo_in_magazine() > 0
end
return false
end
-- raven's merge
-- utility functions related to magazines that shouldn't clutter main mags script
-- MP for ejecting mags on upgrade/replacement
local basePS = itms_manager.play_item_sound --stop pickup item sound from playing when we spawn items during actions.
function itms_manager.play_item_sound(...)
if not magazines.action_in_progress() then
basePS(...)
end
end
DisassemblyWeapon = item_parts.disassembly_weapon
function item_parts.disassembly_weapon(obj, obj_d)
print_dbg("Interdict disassembly")
if obj:weapon_in_grenade_mode() then obj:switch_state(10) end
if get_mag_loaded(obj:id()) then
print_dbg("Found magazine, ejecting")
local mag = eject(obj)
end
create_time_event("mag_redux", "delay_dis", 0.1, wrap_disassembly, obj, obj_d)
end
function wrap_disassembly(obj, obj_d)
DisassemblyWeapon(obj, obj_d)
return true
end
WorkshopUpgrade = ui_workshop.UIWorkshopUpgrade.Upgrade
function ui_workshop.UIWorkshopUpgrade:Upgrade()
local obj = self.CC:GetCell_Selected(true)
if (not obj) then
return
end
-- For weapons, unload mag and clear ammo cache in case of ammo type upgrades
if IsWeapon(obj) and (not IsItem("fake_ammo_wpn",obj:section())) then
if obj:weapon_in_grenade_mode() then obj:switch_state(10) end
if get_mag_loaded(obj:id()) then
local mag = eject(obj)
end
end
create_time_event("mag_redux", "delay_upgr", 0.1, wrap_upgrade, self)
end
function wrap_upgrade(self_obj)
WorkshopUpgrade(self_obj)
return true
end
WorkshopRepair = ui_workshop.UIWorkshopRepair.Repair
function ui_workshop.UIWorkshopRepair:Repair()
local obj = self.CC["inventory"]:GetCell_Selected(true)
if (not obj) then
return
end
if IsWeapon(obj) and (not IsItem("fake_ammo_wpn",obj:section())) then
if obj:weapon_in_grenade_mode() then obj:switch_state(10) end
if get_mag_loaded(obj:id()) then
local mag = eject(obj)
end
end
create_time_event("mag_redux", "delay_rep", 0.1, wrap_repair, self)
end
function wrap_repair(self_obj)
WorkshopRepair(self_obj)
return true
end
CanRepair = inventory_upgrades.can_repair_item
function inventory_upgrades.can_repair_item( sec, cond, mechanic )
if string.find(sec, "mag_") then return false
else return CanRepair(sec, cond, mechanic) end
end
TechUpgrade = ui_inventory.UIInventory.RMode_UpgradeYes
function ui_inventory.UIInventory:RMode_UpgradeYes()
local obj = self.upgr.id and level.object_by_id(self.upgr.id)
if (not obj) then
return
end
if IsWeapon(obj) and (not IsItem("fake_ammo_wpn",obj:section())) then
--artifax fix
if get_mag_loaded(obj:id()) then
local mag = eject(obj)
end
end
TechUpgrade(self)
end
UnloadAll = item_weapon.unload_all_weapons
function item_weapon.unload_all_weapons()
db.actor:iterate_ruck( function(temp,obj)
if IsWeapon(obj) and (not IsItem("fake_ammo_wpn",obj:section())) then
if is_supported_weapon(obj) then
eject(obj)
else
obj:force_unload_magazine(true)
end
end
end)
end
--Patching ui_item.script
--first duplicate some short hand so i can just copy paste
local string_find = string.find
local math_ceil = math.ceil
local math_floor = math.floor
local gc = game.translate_string
local clr_g = utils_xml.get_color("d_green")
local clr_y = utils_xml.get_color("yellow")
local clr_o = utils_xml.get_color("d_orange")
local clr_r = utils_xml.get_color("d_red")
local clr_b = utils_xml.get_color("d_cyan")
local clr_b1 = utils_xml.get_color("pda_blue")
local clr_b2 = utils_xml.get_color("d_blue")
local clr_p = utils_xml.get_color("d_purple")
local clr_w = utils_xml.get_color("pda_white")
local clr_1 = utils_xml.get_color("ui_gray_2")
local clr_2 = utils_xml.get_color("ui_gray_1")
-- mag in, mag out
original_build_desc_header = ui_item.build_desc_header
function ui_item.build_desc_header(obj, sec, str)
local _str = ""
local _str2 = original_build_desc_header(obj, sec, str)
if obj and magazines and IsWeapon(obj) and not IsAmmo(obj) and is_supported_weapon(obj) then
validate_wep(obj:id(), obj:section())
local mag_data = get_mag_loaded(obj:id())
if mag_data then
_str = _str .. " " .. clr_g .. gc("st_dot") .. " ".. gc("st_mag_loaded") .. " " .. clr_2 .. ui_item.get_sec_name(mag_data.section) .. "\\n"
else
_str = _str .. " " .. clr_r .. gc("st_dot") .. " " .. gc("st_mag_loaded_none") .. "\\n"
end
end
_str = _str .. _str2
if obj and magazines and has_loadout_slots(obj) then
local current_id = db.actor:item_in_slot(7) and db.actor:item_in_slot(7):id() or 0
local outfit_slots = get_loadout_slots(obj,true)
if obj:id() == current_id then
local total_slots = get_loadout_size()
local carried = get_total_carried()
for k,v in pairs(carried) do
local total = total_slots[k]
if total > 0 then
local color_ratio = 100 * tonumber(v)/tonumber(total)
print_dbg("Color ratio is %s", color_ratio)
_str = _str .. clr_p .. gc("st_dot") .. " ".. clr_1 .. gc("st_mag_"..k) .. " " .. utils_xml.get_color_con(color_ratio) .. v .. "/" .. total ..(total > outfit_slots[k] and string.format(gc("st_mag_bonus"),outfit_slots[k],total - outfit_slots[k] ) or "") .. "\\n"
end
end
_str = _str .. '\\n'
else
for k,slots in pairs(outfit_slots) do
_str = _str .. clr_p .. gc("st_dot") .. " ".. clr_1 .. gc("st_mag_"..k)..slots.. "\\n"
end
_str = _str .. '\\n'
end
end
return _str
end
-- util function for full, compresses mag data into rounds n type
function collect_mag_data(mag_data, tostr)
local display = {}
local last_round
local count = 0
for i=1,#mag_data.loaded do
if not last_round then last_round = mag_data.loaded[i] end
if last_round ~= mag_data.loaded[i] then
if tostr then
display[#display + 1] = ui_item.get_sec_name(last_round) .. ": " .. count
last_round = mag_data.loaded[i]
else
display[last_round] = count
end
count = 1
else
count = count + 1
end
end
if last_round then
if tostr then
display[#display + 1] = ui_item.get_sec_name(last_round) .. ": " .. count
else
display[last_round] = count
end
end
return display
end
-- mag data
original_build_desc_footer = ui_item.build_desc_footer
function ui_item.build_desc_footer(obj, sec, str)
local _str = ""
local _str2 = original_build_desc_footer(obj, sec, str)
if obj and magazines and is_magazine(obj) then
validate_mag(obj:id(), obj:section())
local mag_data = get_mag_loaded(obj:id())
local level = get_config("mag_tooltip")
print_dbg("Tooltip level %s", level)
_str = _str .. "\\n \\n".. gc('st_mag_size') .. " " .. gc('st_mag_size_'..(SYS_GetParam(0, sec, "mag_size") or "small"))
local mag_capacity = SYS_GetParam(2, sec, "max_mag_size")
local clr = utils_xml.get_color_con(math_floor(100 * #mag_data.loaded/mag_capacity))
if level < 1 then
_str = _str .. "\\n \\n" .. gc('st_mag_capacity') .. " " .. mag_capacity .. clr_2 .. " \\n"
else
_str = _str .. "\\n \\n" .. gc('st_mag_ammo_loaded_count') .. " " .. clr .. #mag_data.loaded .. " / " .. mag_capacity .. clr_2 .. " \\n"
end
if #mag_data.loaded > 0 then
if level < 2 then
_str = _str .. "" .. gc('st_mag_ammo_top_round') .. " " .. clr_g .. ui_item.get_sec_short_name(stack.peek(mag_data.loaded)) .. " \\n"
else
collected = collect_mag_data(mag_data, true)
for i=1,#collected do
_str = _str .. "\\n " .. gc("st_dot").." ".. collected[#collected - i + 1]
end
end
end
end
_str = _str2 .. _str .. " \\n"
return _str
end
--patching item_weapon.script includes a full reimplementation of the ammowheel, could use inheritance, but i don't think this method will cause many problems.
local string_find = string.find
local string_gsub = string.gsub
-------------------------------
-- SCOPES
-------------------------------
CloneWep = _G.alife_clone_weapon
function _G.alife_clone_weapon(se_obj, section, parent_id)
--print_dbg("clone weapon")
if not se_obj then return end
local old_id = se_obj.id
local old_data = get_data(old_id)
local old_sec = se_obj:section_name()
section = section or old_sec
parent_id = parent_id or se_obj.parent_id
local curr_base = get_weapon_base_type(se_obj)
local ammo = utils_item.get_ammo(section, se_obj.id)[1]
--print_dbg("clone weapon1")
local new_wep = CloneWep(se_obj, section, parent_id)
--print_dbg("clone weapon2")
if new_wep and old_data and old_sec then
--print_dbg("clone weapon3")
print_dbg("clone weapon %s:%s to %s:%s", old_sec, old_id, section, new_wep.id )
local same_gun = SYS_GetParam(0, old_sec, "parent_section", old_sec) == SYS_GetParam(0, section, "parent_section", section) --this covers the vanila use of this function for scopes.
if not same_gun then
local caliber_upg = curr_base ~= get_weapon_base_type(old_sec) -- detects caliber change upgrade, if old gun had one new gun will have same resulting in same caliber
local uppg_base_match = caliber_upg and curr_base == ini_file_ex("magazines\\weapons\\importer.ltx"):r_value(section, ammo,0,"") --if there is a caliber upgrade checks that the new guns base type for that ammo is the same
local same_base = uppg_base_match or curr_base == get_weapon_base_type(section) -- Upgrade kits creating guns with the same base type w/o upgrades
same_gun = same_base
end
if same_gun then
print_dbg("transferring data from %s to %s", old_id, new_wep.id)
set_data(new_wep.id, old_data)
else
print_dbg("clone weapon base type miss match spawn mag in inventory: %s", old_data.section )
set_data(new_wep.id, {
section = "no_mag",
loaded = {},
is_weapon = true,
}) -- new gun gets set to empty
local parent = alife_object(parent_id)
local se_mag = parent and alife_create_item(old_data.section, parent)
if se_mag then
old_data.is_weapon = false
set_mag_data(se_mag.id, old_data)
else
print_dbg("Could not create magazine %s", old_data.section)
end
end
end
--set_data(old_id, nil) --let the callback handel this on release.
return new_wep
end
function attach_scope_or(item, weapon)
-- Return if the addon or weapon aren't valid.
if not (item and weapon) then
return
end
if magazines.action_in_progress() then return end
AttachScope(item, weapon)
end
function detach_scope_or(weapon)
-- Return if the weapon is not valid.
if not (weapon) then
return
end
if magazines.action_in_progress() then return end
DetachScope(weapon)
end
function default_mags()
local ids = {}
local function itr_inv(npc, item)
if is_supported_weapon(item) then
local default_mag = weapon_default_magazine(item)
print_dbg("Enjoy your complimentary %s!", default_mag)
id_1 = alife_create_item(default_mag, db.actor)
id_2 = alife_create_item(default_mag, db.actor)
ids[id_1] = default_mag
ids[id_2] = default_mag
end
end
db.actor:iterate_inventory(itr_inv)
return ids
end
NewGameEquipment = itms_manager.new_game_equippment
function itms_manager.new_game_equippment()
default_mags()
return NewGameEquipment()
end
AzazelDeath = gamemode_azazel.actor_on_before_death
function gamemode_azazel.actor_on_before_death(whoID,flags)
AzazelDeath(whoID, flags)
if not flags.ret_value then
create_time_event("mag_redux", "azazel_mags", 0.1, function()
local ids = default_mags()
-- for k,v in pairs(ids) do
-- local mag_data = get_data(k) or create_mag_data(k, v)
-- local max_size = SYS_GetParam(2, v, "max_mag_size")
-- local to_fill = math.random(max_size/3, max_size)
-- local ammo_table = get_magazine_caliber(v)
-- for i=1,to_fill do
-- end
-- set_data(k, mag_data)
-- end
return true
end)
end
end
-- for quick release
function mag_stash(item)
if is_carried_mag(item:id()) then return true end
end
local mag_to_ammo = {
["mag_pm_9x18_default"] = "ammo_9x18_pmm",
["mag_mp5_9x19_default"] = "ammo_9x19_pbp",
["mag_ak_5.45x39_default"] = "ammo_5.45x39_ap",
["mag_ots_9x39_default"] = "ammo_9x39_ap",
["mag_g36_5.56x45_default"] = "ammo_5.56x45_ap"
}
local bar_fights = {
["bar_arena_fight_1"] = {
"mag_pm_9x18_default",
"mag_pm_9x18_default"
},
["bar_arena_fight_2"] = {
"mag_mp5_9x19_default"
},
["bar_arena_fight_4"] = {
"mag_ak_5.45x39_default"
},
["bar_arena_fight_5"] = {
"mag_ak_5.45x39_default",
"mag_ak_5.45x39_default"
},
["bar_arena_fight_6"] = {
"mag_ots_9x39_default",
"mag_ots_9x39_default",
"mag_ots_9x39_default",
"ammo_vog-25"
},
["bar_arena_fight_8"] = {
"mag_g36_5.56x45_default",
"mag_g36_5.56x45_default"
},
}
-- MP for bar fights
BarTele = xr_effects.bar_arena_teleport
function xr_effects.bar_arena_teleport(actor, npc)
BarTele(actor, npc)
-- equip outfit
create_time_event("Mag_redux", "equip_outfit", 0, equip_outfit)
-- create mags and equip
for info, stuff in pairs(bar_fights) do
if has_alife_info(info) then
for k, mag in pairs(bar_fights[info]) do
local se = alife_create(mag,
db.actor:position(),
db.actor:level_vertex_id(),
db.actor:game_vertex_id(),
AC_ID)
if is_magazine(mag) then
create_time_event("Mag_redux", "fill_mag"..se.id, 0, fill_mag, se.id, mag, SYS_GetParam(2, mag, "max_mag_size"), mag_to_ammo[mag])
end
se_save_var( se.id, se:name(), "unpatched", true )
end
break
end
end
end
function equip_outfit()
db.actor:iterate_inventory(function(_, item)
if IsOutfit(item) then
db.actor:move_to_slot(item, 7)
end
end)
return true
end
function fill_mag(id, sec, amount, ammo_type)
local mag_data = get_mag_loaded(id)
if not mag_data then
print_dbg("mag_data not initialized yet for %s, creating now")
mag_data = create_mag_data(id, sec)
end
for i=1,amount do
stack.push(mag_data.loaded, ammo_type)
end
toggle_carried_mag(id)
set_data(id, mag_data)
return true
end
-------------------------------------------------------------------
--GUI = nil -- instance, don't touch --Raven using the GUI in item_weapon.script so that in case someone is acessing it directly it will be in the right place.
local aw_cooldown = 0
local ui_delay = 0 -- small hack to prevent instant keybind action (between switching to next ammo type, and start the wheel again)
local ui_delay_const = 200 -- [ms]
local cache_ammo = {}
local nums_dik = {}
function item_weapon.start_ammo_wheel()
local wpn = db.actor:active_item()
if wpn and IsWeapon(wpn) and (not IsItem("fake_ammo_wpn",wpn:section())) then
hide_hud_inventory()
if (not item_weapon.GUI) then
item_weapon.GUI = UIWheelAmmoWuut()
end
if (item_weapon.GUI) and (not item_weapon.GUI:IsShown()) then
item_weapon.GUI:ShowDialog(true)
item_weapon.GUI:Reset(wpn)
aw_cooldown = time_global()
Register_UI("UIWheelAmmoWuut","wuut_ammo_wheel")-- need to check this.
end
end
end
class "UIWheelAmmoWuut" (CUIScriptWnd)
function UIWheelAmmoWuut:__init() super()
self.object = nil
self.id = nil
self.section = nil
self.ammo_type = nil
self.ammo_list = {}
self.ammo_max = 12
self.show_verybad = (not _NO_DAMAGED_AMMO)
self.ammo_inv = {}
self.avail = {}
self.key = {}
for i=1,9 do
nums_dik[ DIK_keys["DIK_" .. i] ] = i
nums_dik[ DIK_keys["DIK_NUMPAD" .. i] ] = i
end
self:InitControls()
self:InitCallBacks()
end
function UIWheelAmmoWuut:__finalize()
end
function UIWheelAmmoWuut:InitControls()
self:SetWndRect (Frect():set(0,0,1024,768))
self:SetAutoDelete(true)
self:AllowMovement(true)
self.xml = CScriptXmlInit()
local xml = self.xml
xml:ParseFile ("ui_wheel_ammo.xml")
self.dialog = xml:InitStatic("wheel", self)
self.background = xml:InitStatic("wheel:background", self.dialog)
self.extended = xml:InitStatic("wheel:extended", self.dialog)
local box_type = self.show_verybad and ":all" or ":alt"
self.box_r = xml:InitStatic("wheel:result", self.dialog)
self.box_icon_tmp_r = xml:InitStatic("ammo:icon", self.box_r)
self.box = {}
self.box_icon = {}
self.box_icon_r = {}
self.box_icon_tmp = {}
self.box_num = {}
self.box_txt = {}
self.box_txt_r = {}
self.box_btn = {}
self.box_hl_1 = {}
self.box_hl_2 = {}
for i=1,self.ammo_max do
self.box[i] = xml:InitStatic("wheel" .. box_type .. ":box_" .. i, self.dialog)
self.box_hl_1[i] = xml:InitStatic("ammo:highlight", self.box[i])
self.box_hl_2[i] = xml:InitStatic("ammo:highlight", self.box[i])
self.box_icon[i] = xml:InitStatic("ammo:icon", self.box[i])
self.box_icon_tmp[i] = xml:InitStatic("ammo:icon", self.box[i])
self.box_num[i] = xml:InitTextWnd("ammo:num", self.box[i])
self.box_txt[i] = xml:InitTextWnd("ammo:text", self.box[i])
self.box_btn[i] = xml:Init3tButton("ammo:btn", self.box[i])
self:Register(self.box_btn[i],"btn_" .. i)
self.box_icon_r[i] = xml:InitStatic("ammo:icon", self.box_r)
self.box_txt_r[i] = xml:InitTextWnd("ammo:text_r", self.box_r)
end
end
function UIWheelAmmoWuut:InitCallBacks()
for i=1,self.ammo_max do
local _wrapper = function(handler) -- we need wrapper in order to pass ctrl to method
self:OnAmmo(i)
end
self:AddCallback("btn_" .. i, ui_events.BUTTON_CLICKED, _wrapper, self)
end
end
function UIWheelAmmoWuut:Update()
CUIScriptWnd.Update(self)
for i=1,self.ammo_max do
if self.box_btn[i] then
if self.box_btn[i]:IsCursorOverWindow() then
self.box_icon_r[i]:Show(true)
self.box_txt_r[i]:Show(true)
else
self.box_icon_r[i]:Show(false)
self.box_txt_r[i]:Show(false)
end
end
end
end
function UIWheelAmmoWuut:Reset(obj)
self.object = obj
self.id = obj:id()
self.section = obj:section()
self.ammo_type = obj:get_ammo_type()
-- Collect weapon's ammo list
if (not cache_ammo[self.id]) then
cache_ammo[self.id] = utils_item.get_ammo(self.section, self.id)
-- Cut anything with more than 12 ammo types
if (#cache_ammo[self.id] > self.ammo_max) then
for i=self.ammo_max, #cache_ammo[self.id] do
cache_ammo[self.id][i] = nil
end
end
end
self.ammo_list = cache_ammo[self.id]
-- Collect all ammo in inventory
empty_table(self.ammo_inv)
--
if(magazines and obj and is_supported_weapon(obj)) then
self.ammo_inv = magazines.count_magazines(obj)
else
local function itr(temp, itm)
local section = itm:section()
if IsItem("ammo",section) or IsItem("grenade_ammo",section) then
self.ammo_inv[section] = (self.ammo_inv[section] or 0) + itm:ammo_get_count()
end
end
db.actor:iterate_inventory(itr, nil)
end
-- Reset XML elements
self.extended:Show(#self.ammo_list > 9)
--self.box_r:Show(false)
local cnt = 0
empty_table(self.key)
for i=1,self.ammo_max do
local section = self.ammo_list[i]
local found_verybad = section and string.find(section,"verybad") and true or false
if section and ( self.show_verybad or ( (not self.show_verybad) and (not found_verybad) ) ) then
-- Show box and highlighted ammo
local is_curr_ammo = (self.ammo_type == (i - 1))
self.box[i]:Show(true)
self.box_hl_1[i]:Show(is_curr_ammo)
self.box_hl_2[i]:Show(is_curr_ammo)
self.avail[i] = self.ammo_inv[section] and (self.ammo_inv[section] > 0) and true or false
utils_xml.set_icon(section, (not self.avail[i]), self.box_icon[i], self.box_icon_tmp[i])
utils_xml.set_icon(section, nil, self.box_icon_tmp_r, self.box_icon_r[i])
cnt = cnt + 1
self.key[cnt] = i
if self.avail[i] and i <= 9 then
self.box_num[i]:SetText(cnt)
else
self.box_num[i]:SetText("")
end
-- Show ammo count
self.box_txt[i]:SetText("x" .. (self.avail[i] and self.ammo_inv[section] or 0))
self.box_txt_r[i]:SetText( ui_item.get_sec_name(section) )
else
self.avail[i] = false
self.box[i]:Show(false)
end
end
end
function UIWheelAmmoWuut:SwitchNextAmmo()
local wpn = db.actor:active_item()
if wpn and (wpn:section() == self.section) then
local new_type
local ammo_type = wpn:get_ammo_type()
-- Search for available next ammo types
for i=(ammo_type + 2),self.ammo_max do -- +2 because we need next type (+1 is the current type in ammo table)
if self.avail[i] then
new_type = i
break
end
end
-- Search for available earlier ammo types
if (not new_type) then
for i=1,ammo_type do
if self.avail[i] then
new_type = i
break
end
end
end
if new_type then
if(magazines and is_supported_weapon(wpn)) then
local ammo_map = utils_item.get_ammo(nil, wpn:id())
local magazine = magazines.find_magazine(wpn, ammo_map[new_type])
if magazines.get_mag_data(wpn:id()) ~= nil then
print_dbg("Weapon already has magazine, ejecting first")
eject(wpn)
end
local pre_table = magazines.count_ammo(wpn)
wpn:switch_state(7)
disable_info("sleep_active")
local first_round = nil
print_dbg("Mag swap - loaded ammo is %s", wpn:get_ammo_in_magazine())
if magazines_mcm.get_config("retain_round") and wpn:get_ammo_in_magazine() > 0 then
first_round = get_sec_chambered(wpn)
print_dbg("Mag swap - chambered round is %s", first_round)
end
magazines.action_start_reload()
create_time_event("mag_redux", "delay_weapon"..wpn:id(), 0.1, magazines.delay_load_weapon, wpn:id(), magazine, pre_table, first_round)
else
wpn:unload_magazine(true)
wpn:set_ammo_type(new_type - 1) -- ammo type starts from 0
db.actor:reload_weapon()
end
end
end
self:Close()
end
function UIWheelAmmoWuut:OnAmmo(n)
local wpn = db.actor:active_item()
if wpn and (wpn:section() == self.section) and self.avail[n] then
local ammo_type = wpn:get_ammo_type()
if (ammo_type ~= n - 1) then
if(magazines and wpn and is_supported_weapon(wpn)) then
local ammo_map = utils_item.get_ammo(nil, wpn:id())
local magazine = magazines.find_magazine(wpn, ammo_map[n])
if magazines.get_mag_data(wpn:id()) ~= nil then
print_dbg("Weapon already has magazine, ejecting first")
eject(wpn)
end
local pre_table = magazines.count_ammo(wpn)
print_dbg("Mag swap - loaded ammo is %s", wpn:get_ammo_in_magazine())
local first_round = nil
if magazines_mcm.get_config("retain_round") and wpn:get_ammo_in_magazine() > 0 then
first_round = get_sec_chambered(wpn)
print_dbg("Mag swap - chambered round is %s", first_round)
end
wpn:switch_state(7)
disable_info("sleep_active")
magazines.action_start_reload()
create_time_event("mag_redux", "delay_weapon"..wpn:id(), 0.1, magazines.delay_load_weapon, wpn:id(), magazine, pre_table, first_round)
else
wpn:unload_magazine(true)
wpn:set_ammo_type(n - 1) -- ammo type starts from 0
db.actor:reload_weapon()
end
end
end
self:Close()
end
function UIWheelAmmoWuut:OnKeyboard(dik, keyboard_action)
local res = CUIScriptWnd.OnKeyboard(self,dik,keyboard_action)
if (res == false) then
if keyboard_action == ui_events.WINDOW_KEY_RELEASED then
if (time_global() < aw_cooldown + 100) then
return
end
local bind = dik_to_bind(dik)
local num = nums_dik[dik]
if (bind == key_bindings.kWPN_NEXT) then
ui_delay = time_global() + ui_delay_const
self:SwitchNextAmmo()
elseif num and self.key[num] then
self:OnAmmo( self.key[num] )
elseif (bind == key_bindings.kQUIT or bind == key_bindings.kUSE) then
self:Close()
end
end
end
return res
end
function UIWheelAmmoWuut:Close()
if self:IsShown() then
self:HideDialog()
self:Show(false)
Unregister_UI("UIWheelAmmoWuut")
end
end
function on_key_press(key)
if (key == DIK_keys.DIK_LMENU) then
d_flag = true
end
end
function on_key_release(key)
if (key == DIK_keys.DIK_LMENU) then
d_flag = false
end
end
function on_game_start()
--delayed monkey patch because of ishy
DetachScope = item_weapon.detach_scope
item_weapon.detach_scope = detach_scope_or
AttachScope = item_weapon.attach_scope
item_weapon.attach_scope = attach_scope_or
RegisterScriptCallback("on_key_press", on_key_press)
RegisterScriptCallback("on_key_release", on_key_release)
actor_stash_patch.add_condition(mag_stash)
end
@@ -0,0 +1,191 @@
--[[
Dynamic Icon Layers for anomaly inventory. Used by SortingPlus and other mods by RavenAscendant.
4JUL2021
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License
Author: RavenAscendant
--]]
function pr(txt, ...)
-- printf("RAXPH: "..txt, ...)
end
local iconor = aaa_rax_icon_override_mcm and true or false
local icon_override = iconor and aaa_rax_icon_override_mcm.icon_override
local ratio = utils_xml.screen_ratio()
local icon_layers = {}
--functor will be passed cell, obj and section
--functor should return table with the same info as an item section icon layer. {icon_layer = "tch_upgr_ico", icon_layer_x = 0, icon_layer_y = 1, icon_layer_scale = 0.75}
--functor can alternitivly return texture name/file, cords and dimesions {texture = "xxy.dds", x = 0, y = 0, w = 10, h = 10}
function register(name, functor)
if not name then return end -- need all three params and field really needs to be a string
if not icon_layers[name] then
icon_layers[name] = {}
end
table.insert(icon_layers[name], functor)
end
function refresh(mode) --safer way to refresh inventory, mode == 1 refreshes sorting.
local inventory = GetActorMenu()
inventory:UnHighlight_All()
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
--add layers to what gets updated
local base_update = utils_ui.UICellItem.Update
function utils_ui.UICellItem:Update(obj)
obj = obj or (self.ID and level.object_by_id(self.ID))
if (self.showcase == 0) and (not obj) then
self:ResetToChild()
return false
end
local sec = self.section
local xml = self:GetXML()
if obj then
local clsid = obj:clsid()
self:Add_Layers(xml, obj, sec, clsid)
end
return base_update(self, obj)
end
local base_layers = utils_ui.UICellItem.Add_Layers
function utils_ui.UICellItem:Add_Layers(xml, obj, sec, clsid)
base_layers(self, xml, obj, sec, clsid)
local ii = 1
while ((iconor and icon_override:section_exist(sec) and icon_override:r_string_ex(sec,(ii).."icon_layer")) or SYS_GetParam(0, sec, (ii).."icon_layer") ~= nil) do --get ii up to next avail layer.
ii = ii + 1
end
if (not self.layer) then
self.layer = {}
end
for key, name in pairs(icon_layers) do
for _, functor in pairs(name) do
if (not self.layer[ii]) then
if (not xml) then
xml = self:GetXML()
end
self.layer[ii] = xml:InitStatic(self.path .. ":" .. self.cx .. ":pic", self.ico)
end
local tbl = functor and functor(self, obj, sec)
if tbl then
if tbl.icon_layer then
add_icon_layer(self, self.layer[ii], self.ico, sec, tbl)
ii = ii + 1
end
if tbl.texture then
add_texture_layer(self,self.layer[ii], self.ico, sec, tbl)
ii = ii + 1
end
end
end
end
end
function add_icon_layer(self,ele, base, sec_m, tbl)
local sec_l = tbl.icon_layer
local grid_size = self.grid_size
local x = tbl.icon_layer_x or 0
local y = tbl.icon_layer_y or 0
local axis = utils_xml.get_item_axis(sec_l, grid_size)
local w = axis.w
local h = axis.h
local scale = tbl.icon_layer_scale or 1
local scale_pos = scale * (grid_size/50)
local rot = ele:GetHeading() > 0
local x_s = x * ratio * scale_pos
local y_s = y * scale_pos
local w_s = w * ratio * scale
local h_s = h * scale
local w_off = (w_s/2)
local h_off = (h_s/2)
if rot then
-- despite rotation, movement for x and y stays normal!
-- Move start pos to match the one for rotated base icon
local w_b, h_b = base:GetWidth(), base:GetHeight()
local x_st = (w_b/2) - (h_b/2)
local y_st = h_b + x_st
-- On 90 rotation, x and y are inverted, y axis goes negative simulate normal x movement
x_s = x_st + (y * ratio * scale_pos)
y_s = y_st - (x * scale_pos)
w_s = w * scale
h_s = h * scale
w_off = (h_s - h_s *ratio/2)
h_off = -w_s/2
end
ele:InitTexture( utils_xml.get_icons_texture(sec_l) )
ele:SetTextureRect(Frect():set( utils_xml.get_item_axis(sec_l, nil, true) ))
ele:SetStretchTexture(true)
ele:SetWndPos(vector2():set( x_s + w_off , y_s + h_off ))
ele:SetWndSize(vector2():set( w_s , h_s ))
ele:Show(true)
end
function add_texture_layer(self,ele, base, sec_m, tbl)
local grid_size = self.grid_size
local x = tbl.x or 0
local y = tbl.y or 0
local w = tbl.w
local h = tbl.h
local scale = 1
local scale_pos = scale * (grid_size/50)
local rot = ele:GetHeading() > 0
local x_s = x * ratio * scale_pos
local y_s = y * scale_pos
local w_s = w * ratio * scale
local h_s = h * scale
local w_off = (w_s/2)
local h_off = (h_s/2)
if rot then
-- despite rotation, movement for x and y stays normal!
-- Move start pos to match the one for rotated base icon
local w_b, h_b = base:GetWidth(), base:GetHeight()
local x_st = (w_b/2) - (h_b/2)
local y_st = h_b + x_st
-- On 90 rotation, x and y are inverted, y axis goes negative simulate normal x movement
x_s = x_st + (y * ratio * scale_pos)
y_s = y_st - (x * scale_pos)
w_s = w * scale
h_s = h * scale
w_off = (h_s - h_s *ratio/2)
h_off = -w_s/2
end
ele:InitTexture( tbl.texture )
ele:SetStretchTexture(true)
ele:SetWndPos(vector2():set( x_s + w_off , y_s + h_off ))
ele:SetWndSize(vector2():set( w_s , h_s ))
ele:Show(true)
end
@@ -0,0 +1,68 @@
--[[
Persitant Highlights for anomaly inventory. Used by SortingPlus and other mods by RavenAscendant.
24FEB2021
Updated 19Jun21 improved compatablity.
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License
Author: RavenAscendant
--]]
function pr(txt, ...)
-- printf("RAXPH: "..txt, ...)
end
local persistent_highlights = {}
--functor should return an ARGB for the highlight color. recomend low alpha values. name is mostly for aplhabetical priority. will not conflict. there is no unregister, just retun nil if you don't want to use.
function register(name, functor)
if not name then return end -- need all three params and field really needs to be a string
if not persistent_highlights[name] then
persistent_highlights[name] = {}
end
table.insert(persistent_highlights[name], functor)
end
function utils_ui.UICellItem:GetPersistantHighlight()
local clr = nil
for key, name in pairs(persistent_highlights) do
for _, functor in pairs(name) do
clr = functor and functor(self) or clr
pr(key .. ":" .. tostring(clr) .. ":" .. tostring(self.section))
end
end
return clr
end
local base_highlight = utils_ui.UICellItem.Highlight
function utils_ui.UICellItem:Highlight(state, clr_id, main_clr)
if state and (not self:IsShown()) and (not self.manual) then return end
persistant_highlight = self:GetPersistantHighlight()
if (not state) and persistant_highlight then
color = main_clr and change_alpha(persistant_highlight, 255) or persistant_highlight -- if main_clr set the alpha to max for reasons.
self.hl:Show(persistant_highlight or state) -- show if we have a persistant highlight or state is true, else hide.
self.hl:SetTextureColor(color)
else
base_highlight(self, state, clr_id, main_clr)
end
end
clr_cache = {}
function change_alpha(clr, a)
if not clr and a ~= nil then return end
if not clr_cache[clr .. "_" .. a] then
b = bit.band(clr, 255)
g = bit.band(bit.rshift(clr, 8), 255)
r = bit.band(bit.rshift(clr, 16), 255)
clr_cache[clr .. "_" .. a] = GetARGB(a, r, g, b)
end
return clr_cache[clr .. "_" .. a]
end
@@ -0,0 +1,139 @@
--[[
Stacking control
Fixes a bug in utils_ui that allowed things to stack incorectly when the full stacking rules are applied.
Provides a mechanisiam to invoke the full stacking rules on specified items under specified conditions
06Jun2021
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License
Author: RavenAscendant
--]]
local registered_rules = {}
local expanded_rules = {}
-- section_func is a function that when passed a section returns true, if that section should be exempted from the normal forced stack all and have more complex stacking rules applied including custom rules included in the functor
--obj_functor should take an object return true if that particualr object should have the complex staking rules applied, false if that object should not stack with other objects of the same section regardless of other stacking rules.
-- if multiple rules for same
function register(section_func, obj_functor )
if not section_func then return end
registered_rules[section_func] = obj_functor
expanded_rules = {} --clear caching on new rule
end
local function get_rules(section)
--printf("get_rules1:%s %s", section, type(expanded_rules[section]) )
if expanded_rules[section] or expanded_rules[section] == false then return expanded_rules[section] end --false indicates no rules for section, nil is section has not been evaluated
expanded_rules[section] = {}
for k,v in pairs(registered_rules) do
--printf("get_rules2:%s %s", section, k(section) )
if k(section) then
expanded_rules[section][#expanded_rules[section]+1] = v or false --nil is not the same as false
end
end
if #expanded_rules[section]<1 then expanded_rules[section] = false end --don't want to keep an empty list, as empty list is not false
return expanded_rules[section]
end
local function check_rules(obj)
--
local rules = get_rules(obj:section())
if not rules then return false end
local stack = true
for _,v in ipairs(rules) do
stack = stack and (v and v(obj)) --any rule says don't stack it doesn't stack.
end
return not stack
end
function utils_ui.UICellContainer:ValidateSimilar(obj, sec)
if not (obj or sec) then
printe("!ERROR UICellContainer:ValidateSimilar | no data recieved!")
return false
end
local sec = obj:section()
if get_rules(sec) and check_rules(obj) then
return false
end
-- Ignore search if multiuse item is used
local max_uses = IsItem("multiuse",sec)
if max_uses and obj:get_remaining_uses() ~= max_uses then
return false
end
-- Ignore search if item has upgrades
if utils_item.has_upgrades(obj) then
return false
end
return true -- no opbjections let it go
end
function utils_ui.UICellContainer:FindSimilar(obj, sec)
--printf("find similar")
if not (obj or sec) then
printe("!ERROR UICellContainer:FindSimilar | no data recieved!")
return false
end
-- Ignore search if item isn't meant to stack
if SYS_GetParam(1,sec,"dont_stack") then
return false
end
if self.disable_stack then
return false
end
if self.showcase or self.stack_all and (not get_rules(sec)) then
return self:GetCell_SEC(sec)
end
local sec = obj:section() --not sure the importance of this, but tronex had it so make sure it fits.
if not self:ValidateSimilar(obj, sec) then
return false
end
-- items with no condition can stack, return a cell with same section
local clsid = obj:clsid()
local use_cond = SYS_GetParam(1,sec,"use_condition") or IsWeapon(nil,clsid) or IsOutfit(nil,clsid) or IsHeadgear(nil,clsid)
if (not use_cond) and (not get_rules(sec)) then
return self:GetCell_SEC(sec)
end
-- items with condition, full search
for idx,ci in pairs(self.cell) do
local obj_2 = ci.ID and level.object_by_id(ci.ID)
if (ci.section == sec and obj_2 and self:ValidateSimilar(obj_2, ci.section)) then
-- full multiuse item can stack
if max_uses then
return ci
-- item with similar condition can stack
else
local cond = obj:condition()
if obj_2 and math.abs(obj_2:condition() - cond) < 0.1 then
return ci
end
end
end
end
-- no similar item found
return false
end
@@ -0,0 +1,17 @@
function push(table, value)
table[#table + 1] = value
end
function pop(table)
if #table > 0 then
local value = table[#table]
table[#table] = nil
return value
else
return nil
end
end
function peek(table)
return #table == 0 and nil or table[#table]
end
@@ -0,0 +1,201 @@
--[[
Wrapper class to let you autoinject things via monkey patch to all traders, respecting the restock time.
How to use: Monkey patch the update function here in your script.
ex:
TraderAuto = trader_autoinject.update
function trader_autoinject.update(npc)
TraderAuto(npc)
add_custom_crap(npc) -- you define this function ok
end
Some functions provided below for convenience.
Note: If you want to iterate NPC inventory to check for items, fire a time event to allow the items to register on new game.
--]] --
find = string.find
local function t2c(t)
if not t then return nil end
local ct = game.CTime()
ct:set(t.Y,t.M,t.D,t.h,t.m,t.s,t.ms)
return ct
end
local function c2t(ct)
if not ct then return nil end
-- printf('%s, %s',ct,type(ct))
local Y, M, D, h, m, s, ms = 0, 0, 0, 0, 0, 0, 0
Y, M, D, h, m, s, ms = ct:get(Y, M, D, h, m, s, ms)
return { Y=Y, M=M, D=D, h=h, m=m, s=s, ms=ms }
end
TraderUpdate = trade_manager.update
function trade_manager.update(npc, force_refresh)
local id = npc:id()
if not npc:alive() then
return default
end
local reup_time = trade_manager.get_trade_profile(id, "resupply_time")
TraderUpdate(npc, force_refresh)
local restock_time = game_difficulties.get_eco_factor("restock") or 24
if force_refresh then restock_time = 0 end
if reup_time and game.get_game_time():diffSec(t2c(reup_time)) < (restock_time * 3600) then
-- print_dbg("Not time to resupply yet!")
return
end
disable_info("sleep_active")
CreateTimeEvent("custom_update"..npc:id(), "custom_resupply"..npc:id(), 0.1, timed_update, npc)
end
-- Add easier to trace callback
function timed_update(npc)
update(npc)
SendScriptCallback("trader_on_restock",npc)
return true
end
-- monkeypatch me
function update(npc)
end
-- util functions to help with monkey patching
function get_faction_goodwill(faction)
end
COMPANION = 0 -- companions got special trade logic, this is just to catch errors
MECHANIC = 1 -- mechanics/techs
BARMAN = 2 -- exclusive food suppliers like Spirit
MEDIC = 3 -- medics
SUPPLIER = 4 -- everyone else that sells crap
-- return trader type as int, or nil if error
function get_trader_type(npc)
local st = db.storage[npc:id()]
if not st then return -1 end
local trader = false
if npc:character_community() == "trader" or npc:clsid() == clsid.script_trader or npc:clsid() == clsid.trader then
trader = true
end
if find(npc:section(),"trader") then
trader = true
end
local cini = st.ini
local logic = st.section_logic
if not logic and not trader then return -1 end
local trade_logic = cini and cini:r_string_ex(logic, "trade")
if not trade_logic then return -1 end
if find(trade_logic, "companion") then
return COMPANION
elseif find(trade_logic, "trade_generic_mechanic") then
return MECHANIC
elseif find(trade_logic, "trade_generic_barman") then
return BARMAN
elseif find(trade_logic, "trade_generic_medic") then
return MEDIC
else
return SUPPLIER
end
end
-- return supply level of npc, like suppy_1, supply_2, etc
-- as_number removes the supply_ prefix and only returns as int
function supply_level(npc, as_number)
local profile = trade_manager.get_trade_profile(npc:id(), "cfg_ltx")
-- printf("Profile is %s", profile)
local config = trade_manager.get_trade_cfg(profile)
if not config then return end
local str = config:r_string_ex("trader", "buy_supplies")
if not (str) then
return -- no buy_supplies this is normal
end
local condlist = xr_logic.parse_condlist(npc, "trader", "buy_supplies", str)
str = condlist and xr_logic.pick_section_from_condlist(db.actor, npc, condlist)
if as_number then
local num = str_explode(str, "_")
return tonumber(num[2])
else
return str
end
end
-- collapse several tables into one table, the way sections work in ltx files
-- tables should be in section -> amount format
-- precedence goes up to last table, meaning whatever is in the last table will be the last changes applied
function merge_tables(tables)
local final_table = {}
if #tables > 0 then
copy_table(final_table, tables[1])
if #tables > 1 then
for i=2, #tables do
for k,v in pairs(tables[i]) do
final_table[k] = v
end
end
end
end
return final_table
end
local furniture = {
["esc_m_trader"] = true,
["red_m_lesnik"] = true
}
local blacklisted_comms = {
["trader"] = true,
["monster"] = true
}
-- used to get the real community of the NPC by checking spawn id
-- author: HarukaSai
function get_real_community(npc, default)
if furniture[npc:name()] then
return "stalker"
end
local community = character_community(npc)
if not blacklisted_comms[community] then
return community
end
local squad_community = get_object_squad(npc):get_squad_community()
if not blacklisted_comms[squad_community] then
return squad_community
else
return default
end
end
-- to_spawn should be table of sections to amount
-- if check_existing is true, only spawns up to that amount in trader inventory. else arbitrarily spawns
function spawn_items(npc, to_spawn, check_existing)
local npc_name = npc:name()
local alive_or_furniture = xr_conditions.is_alive(db.actor, npc) or furniture[npc_name]
if not alive_or_furniture then return end
local supply_table = {}
copy_table(supply_table, to_spawn)
if check_existing then
local function itr_inv(temp, item)
if supply_table[item:section()] and supply_table[item:section()] > 0 then
-- printf("Found 1 of %s", item:section())
supply_table[item:section()] = supply_table[item:section()] - 1
end
end
npc:iterate_inventory(itr_inv)
end
for k,v in pairs(supply_table) do
-- printf("Creating %s of %s", v, k)
for i=1, v do
-- printf("Created %s", k)
alife_create_item(k, npc)
end
end
end
AddScriptCallback("trader_on_restock")
@@ -0,0 +1,74 @@
local new_art = {}
--[[
categories:
section -> {
section = name of category
articles = section name (title, append _text for info)
}
]]
-- just holds the new categories
local new_cat = {}
_g.ui_pda_autoinject = "blocked" --blocks this script under old name from being loaded.
function parse_cat()
local ini_cat = ini_file("plugins\\encyclopedia_custom\\categories\\importer.ltx")
ini_cat:section_for_each(function(section)
printf("category: processing %s", section)
table.insert(new_cat, section)
end)
local ini_art = ini_file("plugins\\encyclopedia_custom\\articles\\importer.ltx")
ini_art:section_for_each(function(section)
printf("article: processing %s", section)
local line_count = ini_art:line_count(section) or 0
for i=0,line_count-1 do
local junk1, article, category = ini_art:r_line(section, i, "", "")
if not new_art[category] then new_art[category] = {section = category} end
if not new_art[category].articles then new_art[category].articles = {} end
table.insert(new_art[category].articles, article)
end
end)
end
InitArticles = ui_pda_encyclopedia_tab.pda_encyclopedia_tab.InitArticles
function ui_pda_encyclopedia_tab.pda_encyclopedia_tab:InitArticles(section_c)
InitArticles(self, section_c)
if new_art[section_c] then
local category = new_art[section_c]
local articles = category and category.articles or nil
if (not articles) then return end
-- Create each article item and add it to the category.
local item = nil
local section = nil
local n = 1
for i = 1, #articles do
section = articles[i]
if (section) then
local clr = ui_pda_encyclopedia_tab.UpdateColor(section)
item = ui_pda_encyclopedia_tab.pda_encyclopedia_entry(section, n, clr)
self.article_list:AddExistingItem(item)
n = n + 1
end
end
end
end
InitCategories = ui_pda_encyclopedia_tab.pda_encyclopedia_tab.InitCategories
function ui_pda_encyclopedia_tab.pda_encyclopedia_tab:InitCategories()
InitCategories(self)
printf("there are %s new categories", #new_cat)
for k,v in pairs(new_cat) do
local clr = ui_pda_encyclopedia_tab.UpdateColor(v)
item = ui_pda_encyclopedia_tab.pda_encyclopedia_entry(v, k, clr)
self.category_list:AddExistingItem(item)
end
end
function on_game_load()
parse_cat()
end
function on_game_start()
RegisterScriptCallback("on_game_load",on_game_load)
end
@@ -0,0 +1,128 @@
-- -- unused, moved to mag binder
-- print_dbg = magazines.print_dbg
-- set_data = magazine_binder.set_data
-- get_data = magazine_binder.get_data
-- weapon_default_magazine = magazine_binder.weapon_default_magazine
-- validate_wep = magazine_binder.validate_wep
-- is_supported_weapon = magazine_binder.is_supported_weapon
-- get_weapon_base_type = magazine_binder.get_weapon_base_type
-- get_magazine_base_type = magazine_binder.get_magazine_base_type
-- get_config = magazines_mcm.get_config
-- create_time_event = magazines.create_time_event
-- function server_entity_on_register(se)
-- local id = se.id
-- local section = se:section_name()
-- if is_supported_weapon(section) then
-- local mag_data = get_data(id)
-- if valid_mag_data(mag_data) then
-- validate_wep(id, section)
-- end
-- if mag_data == nil then
-- create_time_event("mag_redux", "fill_wep_"..id, 0, timed_fill, id, section)
-- end
-- end
-- end
-- function timed_fill(id, section)
-- local default_mag = weapon_default_magazine(section)
-- mag_data = {}
-- mag_data.section = default_mag
-- mag_data.loaded = {}
-- local ammo_type = 0
-- local ammo_amt = SYS_GetParam(2, default_mag, "max_mag_size") or SYS_GetParam(2, section, "ammo_mag_size") or 999
-- local obj = level.object_by_id(id)
-- if obj then
-- ammo_type = obj:get_ammo_type()
-- ammo_amt = clamp(obj:get_ammo_in_magazine(), 0, ammo_amt)
-- end
-- -- local ammo_type = obj:get_ammo_type()
-- print_dbg("Weapon %s uses mags, assigning default mag %s with %s rounds, type is %s", section, default_mag, ammo_amt, ammo_type)
-- local ammo_map = utils_item.get_ammo(section, id)
-- -- if mag_cap < ammo_loaded then ammo_loaded = mag_cap end
-- for i=1,ammo_amt do
-- print_dbg("Loading in round of type %s",ammo_map[ammo_type+1])
-- stack.push(mag_data.loaded, ammo_map[ammo_type+1])
-- end
-- set_data(id, mag_data)
-- return true
-- end
-- function on_game_start()
-- -- RegisterScriptCallback("server_entity_on_register", server_entity_on_register )
-- end
-- class
-- function bind(obj)
-- obj:bind_object(wep_binder(obj))
-- end
-- class "wep_binder" (object_binder)
-- function wep_binder:__init(obj) super(obj)
-- self.first_update = true
-- end
-- function wep_binder:update(delta)
-- local obj = self.object
-- if not is_supported_weapon(obj) then
-- self.first_update = false
-- return
-- end -- only run for supported weapons
-- local id = obj:id()
-- local mag_data = get_data(id)
-- if mag_data and self.first_update then
-- self.first_update = false
-- validate_wep(id, obj:section())
-- end
-- if mag_data == nil and self.first_update then
-- self.first_update = false
-- local ammo_loaded = obj:get_ammo_in_magazine()
-- --if ammo_loaded == 0 then return end -- might not need this now.
-- local default_mag = weapon_default_magazine(obj:section())
-- mag_data = {}
-- mag_data.section = default_mag
-- mag_data.loaded = {}
-- local ammo_type = obj:get_ammo_type()
-- local mag_cap = SYS_GetParam(2, default_mag, "max_mag_size") or SYS_GetParam(2, obj:section(), "ammo_mag_size") or 999
-- print_dbg("Weapon %s uses mags, assigning default mag %s with %s rounds, type is %s", obj:section(), default_mag, ammo_loaded, ammo_type)
-- local ammo_map = utils_item.get_ammo(nil, id)
-- if mag_cap < ammo_loaded then ammo_loaded = mag_cap end
-- if ammo_loaded > 1 then
-- for i=1,ammo_loaded do
-- print_dbg("Loading in round of type %s",ammo_map[ammo_type+1])
-- stack.push(mag_data.loaded, ammo_map[ammo_type+1])
-- end
-- end
-- set_data(id, mag_data)
-- end
-- end
-- function wep_binder:reload(section)
-- object_binder.reload(self, section)
-- end
-- function wep_binder:reinit()
-- object_binder.reinit(self)
-- end
-- function wep_binder:net_spawn(se_abstract)
-- if not(object_binder.net_spawn(self, se_abstract)) then
-- return false
-- end
-- return true
-- end
-- function wep_binder:net_destroy()
-- object_binder.net_destroy(self)
-- end
-- function wep_binder:save(stpk)
-- end
-- function wep_binder:load(stpk)
-- end
@@ -0,0 +1,240 @@
--[[
Small tweak to item sorting to favor readymags>magazines>everythihng else
--]]
if aaa_rax_icon_override_mcm then end --force load order.
is_magazine = magazine_binder.is_magazine
is_carried_mag = magazine_binder.is_carried_mag
print_dbg = magazines.print_dbg
ini = ini_sys
ui_catagories = {}
item_order = {}
ab_w, ab_h, ab_k = {}, {}, {}
a_sec, a_w, a_h, a_k = nil, nil, nil, nil
b_sec, b_w, b_h, b_k = nil, nil, nil, nil
-- Item order
function set_item_order()
local n = ini:line_count("item_kind_order")
for i=0,n-1 do
local result, kind, order = ini:r_line_ex("item_kind_order",i,"","")
if kind and order then
item_order[kind] = tonumber(order) or 30
end
end
item_order["na"] = size_table(item_order) + 1
end
set_item_order()
function sort_info(asec, bsec)
-- A
a_sec = asec
axis = utils_xml.get_item_axis(a_sec,1)
if (not ab_w[a_sec]) then ab_w[a_sec] = axis.w end
if (not ab_h[a_sec]) then ab_h[a_sec] = axis.h end
if (not ab_k[a_sec]) then
ab_k[a_sec] = SYS_GetParam(0,a_sec,"kind","na")
if (not item_order[ab_k[a_sec]]) then
ab_k[a_sec] = "na"
end
end
a_w = ab_w[a_sec]
a_h = ab_h[a_sec]
a_k = item_order[ab_k[a_sec]]
-- B
b_sec = bsec
axis = utils_xml.get_item_axis(b_sec,1)
if (not ab_w[b_sec]) then ab_w[b_sec] = axis.w end
if (not ab_h[b_sec]) then ab_h[b_sec] = axis.h end
if (not ab_k[b_sec]) then
ab_k[b_sec] = SYS_GetParam(0,b_sec,"kind","na")
if (not item_order[ab_k[b_sec]]) then
ab_k[b_sec] = "na"
end
end
b_w = ab_w[b_sec]
b_h = ab_h[b_sec]
b_k = item_order[ab_k[b_sec]]
end
function sort_by_size(t,a,b)
if (type(t[a]) == "string") then
sort_info(t[a], t[b])
else
sort_info(t[a]:section(), t[b]:section())
end
-- Comparison
--printf("%s - %s", a_sec, b_sec)
if (a_w == b_w) then
if (a_h == b_h) then
if (a_sec == b_sec) then
if (type(t[a]) == "string") then
return false --true
end
return t[a]:id() > t[b]:id()
end
return a_sec < b_sec -- alphaptic order
end
return a_h > b_h
end
return a_w > b_w
end
function sort_by_kind(t,a,b)
if (type(t[a]) == "string") then
sort_info(t[a], t[b])
else
sort_info(t[a]:section(), t[b]:section())
end
if a_k == b_k then
return sort_by_size(t,a,b)
end
return a_k < b_k
end
function sort_by_index(t,a,b)
return t[a].index < t[b].index
end
function sort_by_sizekind(t,a,b)
local a_id = nil
local b_id = nil
if (type(t[a]) == "string") then
sort_info(t[a], t[b])
else
sort_info(t[a]:section(), t[b]:section())
a_id = t[a]:id()
b_id = t[b]:id()
print_dbg("Sorting objects")
end
-- Comparison
--printf("%s - %s", a_sec, b_sec)
if (not(a_id and b_id) or is_carried_mag(a_id) == is_carried_mag(b_id))then
if (not(a_id and b_id) or is_magazine(a_id) == is_magazine(b_id))then
--\\ bigger width wins
if (a_w == b_w) then
--\\ bigger height wins
if (a_h == b_h) then
--\\ important kind wins
if a_k == b_k then
--\\ alphaptic order wins
if (a_sec == b_sec) then
--\\ better condition wins
if (type(t[a]) == "string") then
return false --true
end
return t[a]:condition() > t[b]:condition()
end
return a_sec < b_sec
end
return a_k < b_k
end
return a_h > b_h
end
return a_w > b_w
end
return is_magazine(a_id)
end
return is_carried_mag(a_id)
end
function sort_by_props(t,a,b)
-- Only for objects with same sections
local sec = t[a]:section()
-- For ammo, bigger ammo counts wins
if IsItem("ammo",sec) and (not IsItem("grenade_ammo",sec)) then
return t[a]:ammo_get_count() > t[b]:ammo_get_count()
-- Upgraded items wins
elseif utils_item.has_upgrades(t[a]) and (not utils_item.has_upgrades(t[b])) then
return true
end
-- Better condition wins
return t[a]:condition() > t[b]:condition()
end
utils_ui.sort_by_props = sort_by_props
utils_ui.sort_by_sizekind = sort_by_sizekind
utils_ui.sort_by_index = sort_by_index
utils_ui.sort_by_kind = sort_by_kind
utils_ui.sort_by_size = sort_by_size
utils_ui.sort_info = sort_info
function utils_ui.UICellContainer.FindFreeCell(self,obj, sec)
if (not sec) then
if (not obj) then
return false
end
sec = obj and obj:section()
end
axis = utils_xml.get_item_axis(sec,1)
local w = axis.w
local h = axis.h
-- Avoid icons that don't fit
if w > self.cols then
return false
end
-- Sorting by kind: when sorting a new kind, always start from last row taken by previous kind
if self.sort_method == "kind" then
self.rKind.current = item_order[ab_k[sec]]
if (self.rKind.last ~= self.rKind.current) then
--[[
local cnt = self.line_cnt + 1
if (not self.line[cnt]) then
self.line[cnt] = self.xml:InitStatic(self.path .. ":line", self.st)
end
local y = (self.row_end) * (self.grid_size + self.grid_line)
self.line[cnt]:SetWndPos( vector2():set(0,y - 2.5) )
self.line[cnt]:SetWndSize( vector2():set(self.prof:GetWidth(),6) )
self.line[cnt]:Show(true)
self.line_cnt = cnt
--]]
self.rKind.last = self.rKind.current
self.rKind.row = self.row_end + 1
end
end
local row_s = self.rKind.row
local rows = #self.grid
local cols = self.cols + 1 - w
self:Print(nil, "FindFreeCell for [%s] (rows: %s, cols: %s, W: %s, H: %s)", sec, rows,cols,w,h)
for r=row_s, rows do
for c=1,cols do
if self:IsFreeRoom(r,c,w,h) then
return self:TakeRoom(r,c,w,h)
end
end
end
self:Grow()
return self:FindFreeCell(obj, sec)
end