Divergent/mods/Lootboxes/gamedata/scripts/arti_lootboxes.script

929 lines
32 KiB
Plaintext
Raw Normal View History

2024-03-17 20:18:03 -04:00
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