local gc = game.translate_string local rand = math.random local send_tip = news_manager.send_tip local get_config = arti_lootboxes_mcm.get_config -- test to see if scaling to 1000 helps or not local NORMALIZE_1000 = true -- importer for lootbox -> loot pool ini_contents = ini_file("items\\lootboxes\\box_contents\\importer.ltx") -- importer for loot pool -> contents ini_pools = ini_file("items\\lootboxes\\item_pools\\importer.ltx") ini_uniques = ini_file("items\\lootboxes\\unique_contents\\importer.ltx") ini_lootbox = ini_file("items\\items\\items_lootbox.ltx") ini_tools = ini_file("items\\settings\\lootbox_tools.ltx") -- custom loaded contents contents_custom = {} pools_custom = {} -- cached maps, these will get hydrated as more loot is rolled local pool_cache = {} local contents_cache = {} -- map box groups to boxes local variant_map = {} -- track boxes that contain uniques, as well as what uniques have been seen so far local uniques_cache = {} -- store all lootbox contents local lootbox_contents = {} -- stolen from treasure_manager local item_prop_table = { cond_r = {30,70} , cond_ct = "part" , cond_cr = {0.5,0.75,1} } map_tiers = {} local function save_state(mdata) mdata.lootbox_contents = lootbox_contents end local function load_state(mdata) lootbox_contents = mdata.lootbox_contents or {} if not lootbox_contents.uniques then lootbox_contents.uniques = {} end end -- unregister boxes that are deleted local function server_entity_on_unregister(se_obj) if lootbox_contents[se_obj.id] then lootbox_contents[se_obj.id] = nil end if not lootbox_contents.uniques then lootbox_contents.uniques = {} end -- if a unique box is deleted, add it back to the pool if se_obj and lootbox_contents.uniques[se_obj.id] then lootbox_contents.uniques[se_obj.id] = nil if not uniques_cache[se_obj:section_name()] then return end local unique = lootbox_contents.uniques[se_obj.id] if unique and se_obj:section_name() then uniques_cache[se_obj:section_name()][unique] = true end end end function print_dbg(text, ...) if get_config("debug") then printf("lootboxes | %s | " .. text, time_global(), ...) end end function get_loot_string(id) return lootbox_contents[id] end -- rand() against these odds function roll(odds) if not odds then callstack() return false end if NORMALIZE_1000 then odds = math.ceil(1000*tonumber(odds)) return rand(1000) <= odds else return rand() <= tonumber(odds) end end -- roll a cond between min and 2x min, clamped at 99 function roll_cond(min) return rand(min, clamp(2*min, 0, 99)) end -- return random entry from integer-indexed table function random_entry(t) t = t or {} return t[rand(#t)] end -- convert a comma-separated string of sections and numbers into a table function parse_lootstring(str) local temp = str_explode(str, ",") local t = {} for i=1,#temp-1, 2 do table_upsert(t, temp[i], temp[i+1]) end return t end -- convert a table into a comma-separated string of sections and numbers function create_lootstring(template) str = "" for k,v in pairs(template) do str = str .. k .. "," .. v .. "," end return str end -- str_explode but they get parsed into numbers function str_explode_num(str, sep, plain) if not (str and sep) then printe("!ERROR str_explode | missing parameter str = %s, sep = %s",str,sep) callstack() end if not (sep ~= "" and str:find(sep,1,plain)) then return { tonumber(str) } end local t = {} local size = 0 for s in str:gsplit(sep,plain) do size = size + 1 t[size] = tonumber(trim(s)) end return t end function table_upsert(t, k, v) if type(v) == "number" then t[k] = t[k] and t[k] + v or v else t[k] = v end end -- coerce the input into the section, or nil local function get_sec(obj) if not obj then return end return type(obj) == "string" and obj or (type(obj.id) == "function" and obj:section() or nil) end local not_wpn = { ["WP_BINOC"] = true, ["WP_SCOPE"] = true, ["WP_SILEN"] = true, ["WP_GLAUN"] = true } function sec_is_weapon(section) local class = SYS_GetParam(0, section, "class") if string.find(section, "wpn_") then return not_wpn[class] == nil else return false end end function is_box(obj, sec) sec = obj and obj:section() or sec return SYS_GetParam(0, sec, "open_with") ~= nil end function is_tool(obj, sec) sec = obj and obj:section() or sec return ini_tools:section_exist(sec) end function init() local ini_map = ini_file("items\\settings\\lootbox_map_distribution.ltx") ini_map:section_for_each(function(map) if not map_tiers[map] then map_tiers[map] = {} end print_dbg("processing map %s", map) local line_count = ini_map:line_count(map) or 0 local ref = map_tiers[map] for i=0,line_count-1 do local drop_chance = {} local junk1, group, chance = ini_map:r_line_ex(map, i, "", "") drop_chance.group = group drop_chance.chance = chance or 0.5 print_dbg("adding box group %s to map %s, weight %s", group, map, chance) ref[i + 1] = drop_chance end end) local ini_stalker = ini_file("items\\settings\\lootbox_stalker_distribution.ltx") ini_stalker:section_for_each(function(faction) if not map_tiers[faction] then map_tiers[faction] = {} end print_dbg("processing faction %s", faction) local line_count = ini_stalker:line_count(faction) or 0 local ref = map_tiers[faction] for i=0,line_count-1 do local drop_chance = {} local junk1, items, chance = ini_stalker:r_line_ex(faction, i, "", "") drop_chance.items = items drop_chance.chance = chance or 0.5 ref[i + 1] = drop_chance end end) ini_lootbox:section_for_each(function(section) local group = ini_lootbox:r_string_ex(section, "box_group") or "low" if group then if not variant_map[group] then variant_map[group] = {} end table.insert(variant_map[group], section) end end) init_uniques() end function init_uniques() -- hydrate the box uniques that have not been seen so far -- mechanism: initially add all sections to this cache -- when a unique box rolls, add an entry in uniques child table associating box id to what unique section exactly -- when that box is opened (or deleted), drop the entry -- opening also marks that entry as being unable to drop again ini_uniques:section_for_each(function(section) if not lootbox_contents.uniques then lootbox_contents.uniques = {} end if lootbox_contents.uniques[section] then return end local box = ini_uniques:r_string_ex(section, "box") if not uniques_cache[box] then uniques_cache[box] = {} end uniques_cache[box][section] = true end) end -- Weapon data is stored as weapon_section,flags_ammotype_cond function append_weapon(template, section, min, add_ammo) template = template or {} if not sec_is_weapon(section) then print_dbg("Section %s is not weapon! Returning only 1" , section) table_upsert(template, section, 1) return end if string.find(section, "knife") or string.find(section, "axe") then print_dbg("Appending melee weapon") table_upsert(template, section, "0_0_"..roll_cond(min)) return end local loot_str = "" local flag = 0 if (ini_sys:r_float_ex(section,"scope_status")) then flag = flag + 1 end if (ini_sys:r_float_ex(section,"grenade_launcher_status")) then flag = flag + 2 end if (ini_sys:r_float_ex(section,"silencer_status")) then flag = flag + 4 end flag = rand(0,flag) ammos = parse_list(ini_sys,section,"ammo_class") ct = ammos and #ammos ammo_type = ammos and ct and rand(0,ct-1) or 0 ammo_section = ammo_type and ammos[ammo_type+1] local condition = roll_cond(min) print_dbg("Appending weapon of type %s", section) table_upsert(template, section, flag.. "_"..ammo_type.."_"..condition) if add_ammo and ammos and ct and ct > 0 then table_upsert(template, ammos[1], rand(3)) end return template end -- on the chance that all maxes don't sum up, cut after this many iterations local MAX_ITER = 10000 -- build a loot template to be substituted with items -- loot template is table mapping item pools = quant of items function build_template(template, section, max, bias) template = template or {} if ini_contents:section_exist(section) then -- cache the table if not contents_cache[section] then n = ini_contents:line_count(section) local pool = {} for i=0,n-1 do local content = {} _, id, value = ini_contents:r_line_ex(section,i,"","") print_dbg("Adding %s of %s", value, id) values = str_explode_num(value, ",") content.section = id content.size = values[1] content.limit = values[2] content.chance = values[3] table.insert(pool, content) end if contents_custom[section] and not is_empty(contents_custom[section]) then print_dbg("Adding %s custom entries for section %s", #contents_custom[section], section) for k,v in pairs(contents_custom[section]) do table.insert(pool, v) end end contents_cache[section] = pool end -- build max = (max and max > 0) and max or 10 bias = bias or 1 bias = bias * get_config("lootquality") print_dbg("Adding %s items, bias %s", max, bias) content_pool = contents_cache[section] iters = 0 while max > 0 do -- pick a random entry from the pool if iters > MAX_ITER then break end item = random_entry(content_pool) print_dbg("Rolled item %s, chance is %s", item.section, item.chance) local curr_amt = template[item.section] or 0 if curr_amt < item.limit and item.size <= max and roll(item.chance * bias) then table_upsert(template, item.section, 1) max = clamp(max - item.size, 0, max) print_dbg("Added %s, (size %s), %s left", item.section, item.size, max) end iters = iters + 1 end end end -- sub item pools for actual items in content template function process_template(template, box_section) local processed = {} for sec,v in pairs(template) do for j=1,v do local id, value = random_item_entry(sec) print_dbg("Adding %s amount of %s to lootbox",value, id) if sec_is_weapon(id) then local condition = SYS_GetParam(2, box_section, "weapon_condition") or rand(20, 80) append_weapon(processed, id, condition) elseif item_device.device_npc_pda[id] then if not processed[id] then local schmuck_id = pda_custom.cache_local_schmuck() print_dbg("Adding %s's pda to the loot", schmuck_id) if schmuck_id ~= 0 then table_upsert(processed, id, schmuck_id) end end else local max_multiuse = SYS_GetParam(1, box_section, "multiuse_full") or false local max_uses = SYS_GetParam(2, id, "max_uses") or 1 if not max_multiuse and max_uses > 1 then id = id .. "__" .. rand(max_uses) end table_upsert(processed, id, value) end end end return processed end -- select a random item from the item pool function random_item_entry(section) print_dbg("Adding item from item pool %s", section) if ini_pools:section_exist(section) then if not pool_cache[section] then n = ini_pools:line_count(section) local contents = {} for i=0,n-1 do local ref = {} _, id, value = ini_pools:r_line_ex(section,i,"","") if not value or value == "" then value = 1 end id = str_explode(id, ",") ref.section = id[1] ref.amount = id[2] or 1 ref.chance = value contents[#contents + 1] = ref end if pools_custom[section] and not is_empty(pools_custom[section]) then print_dbg("Adding %s custom entries for section %s", #pools_custom[section], section) for k,v in pairs(pools_custom[section]) do table.insert(contents, v) end end pool_cache[section] = contents -- end end local item_pool = pool_cache[section] local selection = nil while not selection do local item = random_entry(item_pool) if roll(item.chance) then selection = item end end return selection.section, selection.amount else printf("!!Content section %s not found!!", section) return "duct_tape",1 end end -- attempt to populate a unique. if it fails, we can provide normal contents function try_populate_unique(box_id, box_section) u = uniques_cache[box_section] if not u or next(u) == nil then return "" end if rand(10) ~= 10 then return "" end local unique_sec = random_key_table(u) local unique_contents = ini_uniques:r_string_ex(unique_sec, "contents") print_dbg("Putting custom contents %s into box %s", unique_contents, box_section) if not lootbox_contents.uniques then lootbox_contents.uniques = {} end lootbox_contents.uniques[box_id] = unique_sec lootbox_contents[box_id] = unique_contents if (box_section == "lootbox_4") then lootbox_contents.spooky = box_id end uniques_cache[box_section][unique_sec] = nil return unique_contents end function populate_lootbox(box_id, box_section) if not lootbox_contents[box_id] then print_dbg("Populating lootbox loot for %s of type: %s",box_id, box_section) local loot_type = SYS_GetParam(0,box_section,"loot_type") print_dbg("Loot type is %s", loot_type) -- populate lore box, 10% chance local loot_str = try_populate_unique(box_id, box_section) if loot_str ~= "" then -- no action required elseif loot_type == "weapon" then -- spawn the weapon, roll for condition + attachments + spare ammos local weapon_tbl = {} local loot_section = SYS_GetParam(0, box_section, "contents") local section, value = random_item_entry(loot_section, true) local condition = SYS_GetParam(2, box_section, "weapon_condition") or rand(20, 80) append_weapon(weapon_tbl, section, condition, rand(2) == 1) loot_str = loot_str .. create_lootstring(weapon_tbl) elseif loot_type == "grab" then local box_template = {} -- grab a template, iterate through the pairs of items and populate bsaed on loot local loot_sec = SYS_GetParam(0, box_section, "contents") local min, max = unpack(str_explode_num(SYS_GetParam(0, box_section, "items_range"), ",")) local to_roll = rand(min, max) local b_min, b_max = unpack(str_explode_num(SYS_GetParam(0, box_section, "bias_range"), ",")) or 1, 2 -- bias skews the drop rate for rare stuff up if less items spawn local bias = b_min + ( (b_max - b_min) * clamp(max - to_roll, 0, 999) / clamp(max - min, 1, 999) ) build_template(box_template, loot_sec, to_roll, bias) local loot_template = process_template(box_template, box_section) loot_str = loot_str .. create_lootstring(loot_template) end lootbox_contents[box_id] = loot_str print_dbg("Lootbox final contents for %s: %s", box_id, loot_str) end end function actor_on_item_take_from_box(box,itm) local id = box:id() if lootbox_contents[id] == true then lootbox_contents[id] = nil end end function spook_player(obj) if lootbox_contents.spooky == obj:id() then send_tip(db.actor, gc("st_spooky_"..rand(4)), nil, "swiss_knife", 6000) end end function on_item_drag_dropped(from, to, slot_from, slot_to) if not (slot_from == EDDListType.iActorBag and slot_to == EDDListType.iActorBag) then return end try_open_box(to, from) end function check_open_compatibility(box, tool) box = get_sec(box) tool = get_sec(tool) local open_type = ini_tools:r_string_ex(tool, "open_type") or "nope" -- not a box local box_open_type = parse_list(ini_lootbox, box, "open_with", true) print_dbg("Checking tool %s (open type %s) against box %s", tool, open_type, box) return box_open_type[open_type] end -- check if actor can even open the box -- returns a table of result and reason (reason is passed to actor) -- preconds need the same! function check_open_box(box, tool) -- coerce into section box_sec = get_sec(box) tool_sec = get_sec(tool) -- not a tool if not check_open_compatibility(box_sec, tool_sec) then return { result = false, reason = "st_incompatible"} end local precond = ini_tools:r_string_ex(tool_sec, "precondition") or "arti_lootboxes.no_precond" precond = str_explode(precond,"%.") if _G[precond[1]] and _G[precond[1]][precond[2]] then return _G[precond[1]][precond[2]](box, tool) else return { result = false, reason = "st_incompatible"} end end -- attempt to use the tool on the box and open it function try_open_box(box, tool) -- tool/box validation here if not is_box(box) or not is_tool(tool) then return false end local res = check_open_box(box, tool) if res.result then open_lootbox(box, tool) post_open_box(box, tool) else -- tool not compatible/insufficient send_tip(db.actor, gc(res.reason), nil, "swiss_knife", 6000) end end -- check postcondition function post_open_box(box, tool) tool_sec = get_sec(tool) local postcond = ini_tools:r_string_ex(tool_sec, "postcondition") or "arti_lootboxes.no_precond" postcond = str_explode(postcond,"%.") if _G[postcond[1]] and _G[postcond[1]][postcond[2]] then _G[postcond[1]][postcond[2]](box, tool) end end function no_precond() return { result = true } end -- for patchers function get_difficulty(box, tool) box = get_sec(box) tool = get_sec(tool) local diff = SYS_GetParam(2, box, "difficulty") or 1 if db.actor:object("lockpick_set") then diff = clamp(diff - 1, 1, 10) end return diff end -- lockpicks, check if difficulty amount of picks are present function precond_lockpick(box, tool) box_sec = get_sec(box) tool_sec = get_sec(tool) local difficulty = get_difficulty(box_sec, tool_sec) local amt = 0 local function search(temp, item) if item:section() == tool_sec then amt = amt + 1 end end db.actor:iterate_inventory(search) if amt == 0 then return { result = false, reason = "st_no_picks" } end amt = amt > 10 and 10 or amt if difficulty > amt then return { result = false, reason = "st_cant_unlock"} else return {result = true} end end -- consume X picks function use_lockpick(box, tool) box_sec = get_sec(box) tool_sec = get_sec(tool) local difficulty = get_difficulty(box_sec, tool_sec) local amt = 0 local function search(temp, item) if item:section() == tool_sec and amt < difficulty then alife_release_id(item:id()) amt = amt + 1 end end db.actor:iterate_inventory(search) end function use_axe(box, tool) -- smesh if (tool:condition() > 0.25) then tool:set_condition(clamp(tool:condition() - 0.25, 0, 1)) else -- blin, axe broke send_tip(db.actor, gc("st_rip_axe"), nil, "swiss_knife", 6000) alife_release_id(tool:id()) end end function precond_snapgun(box, tool) box_sec = get_sec(box) tool_sec = get_sec(tool) local difficulty = get_difficulty(box_sec, tool_sec) local power = ini_tools:r_float_ex(tool_sec, "open_power") if difficulty > power then return { result = false, reason = "st_cant_unlock_snapgun" } else return { result = true } end end function release_tool(box, tool) alife_release_id(tool:id()) end function str_pick(box) local tool = db.actor:object("lockpick") if tool and check_open_compatibility(box, "lockpick") then return "st_unlock_pick" end end function do_pick(box) try_open_box(box, db.actor:object("lockpick")) end function str_snap(box) local tool = db.actor:object("snapgun") if tool and check_open_compatibility(box, tool) then return "st_unlock_snap" end end function do_snap(box) try_open_box(box, db.actor:object("snapgun")) end function str_coin(box) local tool = db.actor:object("arcade_tokens") if tool and check_open_box(box, tool).result then return "st_unlock_coin" end end function do_coin(box) try_open_box(box, db.actor:object("arcade_tokens")) end -- Weapon data is stored as weapon_section,flags_ammotype_cond function give_weapon(weapon, attachment_data) if weapon == "wpn_toz194" then weapon = "wpn_wincheaster1300" end local table = str_explode(attachment_data, "_") local se_obj = alife_create(weapon,db.actor:position(),db.actor:level_vertex_id(),db.actor:game_vertex_id(),db.actor:id(),false) local data = utils_stpk.get_weapon_data(se_obj) local cond = tonumber(table[3]) or 75 if (data) then data.condition = (cond/100) data.addon_flags = tonumber(table[1]) data.ammo_type = tonumber(table[2]) utils_stpk.set_weapon_data(data, se_obj) end alife():register(se_obj) print_dbg("Granting weapon of type %s", weapon) local name = SYS_GetParam(0, weapon, "inv_name") local quality = clamp(math.floor(cond / 25), 0, 3) local message = gc("st_weapon_"..quality).." ".. gc(name) .. "\\n" -- send_tip(db.actor, message, nil, "swiss_knife", 6000) return message end function open_lootbox(box, tool) local id = box:id() if not (lootbox_contents[id]) then print_dbg("Lootbox contents not found. Populating.") populate_lootbox(id, box:section()) end -- play cool animation, with cool sound if get_config("animation") then tool_sec = get_sec(tool) local sound = ini_tools:r_string_ex(tool_sec, "sound") or "Plastic_Small_Pick" local delay = ini_tools:r_float_ex(tool_sec, "duration") or 5 CreateTimeEvent("lootbox", "disable_ui", 0, function() xr_effects.disable_ui_only(db.actor, nil) return true end) utils_obj.play_sound(sound) print_dbg("Start open box with id "..id) RemoveTimeEvent("lootbox","box_open " .. id) CreateTimeEvent("lootbox","box_open " .. id, delay, function(id, give) xr_effects.enable_ui_lite(db.actor, nil) open_lootbox_timer(id, give) return true end, id, true) else RemoveTimeEvent("lootbox","box_open " .. id) CreateTimeEvent("lootbox","box_open " .. id, 0.1, function(id, give) open_lootbox_timer(id, give) return true end, id, true) end end function open_lootbox_timer(id, give) if not lootbox_contents[id] then return false end print_dbg("Opening box with id "..id) local spawned_items = parse_lootstring(lootbox_contents[id]) local str = gc("st_lootbox_get") .. ":\\n" -- give loot for section, quantity in pairs(spawned_items) do print_dbg("Creating %s of %s", quantity, section) if section == "spooky" then alife_create("m_poltergeist_normal_flame", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id()) send_tip(db.actor, gc("st_spooky_free"), nil, "swiss_knife", 6000) lootbox_contents.spooky = nil elseif section == "money" then local money_amt = tonumber(quantity) or 5000 db.actor:give_money(money_amt) str = str ..money_amt .. " ".. gc("st_money") .. "\\n" elseif sec_is_weapon(section) then str = str .. give_weapon(section, quantity) elseif item_device.device_npc_pda[section] then local se_itm = alife_create_item(section, db.actor, item_prop_table) local id = se_itm and se_itm.id -- quantity will be the id of the schmuck this pda belongs to pda_custom.register_pda(tonumber(quantity), section, id) else if section == "leatherman" then section = "leatherman_tool" end local amt = tonumber(quantity) or 1 for j=1,amt do local se_itm = alife_create_item(section, db.actor, item_prop_table) end if string.find(section, "__") then section = str_explode(section, "__")[1] end local item_name = ui_item.get_sec_name(section) or "of something" if string.find(section, "ammo") then local str_ammos = amt > 1 and "st_loot_ammos" or "st_loot_ammo" str = str.. amt.. " ".. gc(str_ammos) .. " "..gc(item_name).. "\\n" else str = str.. amt.. " "..gc(item_name).. "\\n" end end end send_tip(db.actor, str, nil, "swiss_knife", 6000) if give then lootbox_contents[id] = nil -- manage uniques - once player opens, remove unique from appearing again if not lootbox_contents.uniques then lootbox_contents.uniques = {} end if lootbox_contents.uniques[id] then local unique_sec = lootbox_contents.uniques[id] lootbox_contents.uniques[id] = nil lootbox_contents.uniques[unique_sec] = true print_dbg("Lootbox unique %s opened", unique_sec) end lootbox_contents[id] = nil alife_release_id(id) end end -- given a group id, return a lootbox section from that group, if applicable function pick_lootbox(map) if map_tiers[map] then local group = nil local bias = get_config("lootquality") or 1 while not group do local m = random_entry(map_tiers[map]) if roll(m.chance * bias) then group = m.group end end return random_entry(variant_map[group]) end end -- spawn lootbox in box, tie the contents of box to creation time to prevent RNG cheese function spawn_lootbox(box) local id = box:id() if lootbox_contents[id] then return end local roll = rand(100) if roll <= get_config("stashchance") then local se_obj = alife_object(id) local lvl = alife():level_name(game_graph():vertex(se_obj.m_game_vertex_id):level_id()) print_dbg("spawning box in %s", lvl) local box_type = pick_lootbox(lvl) if ini_sys:section_exist(box_type) then print_dbg("Spawned in lootbox of type "..box_type) se_itm = alife_create_item(box_type, box) -- populate_lootbox(se_itm.id, "lootbox_"..box_type) else print_dbg("Could not spawn lootbox of type "..box_type) end end if roll <= 10 then local section = "lockpick" local roll = rand(20) -- 70% one pick, 25% bundle, 5% skelekey if roll == 20 then section = "skeleton_key" -- elseif roll > 16 then -- section = "bundle_lockpick" else section = "lockpick" end local se_obj = alife_create(section,box:position(),box:level_vertex_id(),box:game_vertex_id(),box:id(),false) alife():register(se_obj) end -- set box id to true to mark that is has been checked lootbox_contents[id] = true end -- spawn items on dead people function spawn_stalker_loot(npc) if rand(100) <= get_config("deathchance") then local community = npc:character_community() local rank_bias = (npc:rank() / 10000) or 1 if map_tiers[community] == nil then community = "stalker" end local table = map_tiers[community] local to_drop = nil local iters = 0 while not to_drop do local m = random_entry(map_tiers[community]) if not m.chance then m.chance = 0.5 end if roll(m.chance * rank_bias) then to_drop = m.items end end tbl = str_explode(to_drop, ",") if not tbl[2] then tbl[2] = 1 end print_dbg("Spawning %s of %s on dead NPC", tbl[2], tbl[1]) for i=1,tonumber(tbl[2]) do se_obj = alife_create(tbl[1], npc:position(), npc:level_vertex_id(), npc:game_vertex_id(), npc:id(), false) alife():register(se_obj) end end end -- monkey patch loot managers SpawnTreasure = treasure_manager.try_spawn_treasure function treasure_manager.try_spawn_treasure(box) local id = box:id() --printf("try_spawn_treasure [%s]",caches[id]) -- no spawn if the cache is already looted if not (treasure_manager.caches[id]) then return end spawn_lootbox(box) SpawnTreasure(box) end -- string stuff function build_diff(box_sec) local difficulty = get_difficulty(box_sec) * 10 local clr = utils_xml.get_color_con(100 - tonumber(difficulty)) return clr .. " " .. gc("st_dot").. " " .. utils_xml.get_color("ui_gray_1") .. gc("st_box_difficulty") .. " " .. clr .. tostring(difficulty) .. "%" .. "\\n \\n" .. utils_xml.get_color("ui_gray_1") end function build_coins(box_sec) local clr = utils_xml.get_color_con(0) return clr .. " " .. gc("st_dot").. " " .. utils_xml.get_color("ui_gray_1") .. gc("st_box_difficulty") .. " " .. clr .. "??????" .. "\\n \\n" .. utils_xml.get_color("ui_gray_1") end function build_monoloot(box_sec) local clr = utils_xml.get_color_con(0) return clr .. " " .. gc("st_dot").. " " .. utils_xml.get_color("ui_gray_1") .. gc("st_box_difficulty") .. " " .. clr .. "9001%" .. "\\n \\n" .. utils_xml.get_color("ui_gray_1") end -- get custom display of difficulty function build_string(box_sec) local precond = SYS_GetParam(0, box_sec, "diff_str") or "arti_lootboxes.build_diff" precond = str_explode(precond,"%.") if _G[precond[1]] and _G[precond[1]][precond[2]] then return _G[precond[1]][precond[2]](box_sec) else return "" end end BuildFooter = ui_item.build_desc_footer function ui_item.build_desc_footer(obj, sec, str) str = str or gc(ini_sys:r_string_ex(sec,"description")) if (not str) then return "" end if is_box(nil, sec) then return str .. build_string(sec) else return BuildFooter(obj, sec, str) end end CreateReleaseItem = death_manager.create_release_item function death_manager.create_release_item(npc) CreateReleaseItem(npc) spawn_stalker_loot(npc) end function on_game_start() if (USE_MARSHAL) then RegisterScriptCallback("save_state",save_state) RegisterScriptCallback("load_state",load_state) end RegisterScriptCallback("actor_on_item_take_from_box",actor_on_item_take_from_box) RegisterScriptCallback("ActorMenu_on_item_drag_drop",on_item_drag_dropped) RegisterScriptCallback("server_entity_on_unregister", server_entity_on_unregister) RegisterScriptCallback("actor_on_item_take",spook_player) init() end