-- ============================================================ -- -- coc_treasure_manager.script -- CoC 1.4.15 - DoctorX Questlines 1.13 -- -- Modified by: DoctorX -- Last revised: November 10, 2016 -- -- ============================================================ --[[ ------------------------------------------------------------------------------------------------------------------ -- Treasure randomizer -- by Alundaio ------------------------------------------------------------------------------------------------------------------ This script spawns loot, randomly, inside all INVBOX class objects. Loot is determined on new game start and stored within the .scoc as a string. When the cache is opened, the loot is spawned. A list of all valid item sections is generated automatically by parsing all of system_ini(). From this list, loot can be obtained. If you do not want a certain section from being spawned, put it in 'plugins\coc_treasure_manager.ltx' 'ignore_sections'. A valid item section is determined by several factors: 1. It must have can_trade = true 2. It must have quest_item = false 3. It must not have '_mp' in it's section name 4. It must have a valid 'inv_name' value 5. 'cost' must be greater than 0 To debug or to obtain a list of valid item sections, simply enable DEV_DEBUG (-dbg in command line). You shall find a 'valid_item_sections.ltx' in your main game directory after you start a new game. To debug the actual results, start a new game. Then while in the loadscreen menu ctrl+left-click on the new 'autosave'. You will find the .lua in your savegame folder. Search the file for 'caches' --]] caches = {} local caches_count = 0 local ltx_base = ini_file("plugins\\coc_treasure_manager.ltx") local valid_item_list = nil ------------------------------------------------------------------------------------------------------------------ -- PRIVATE ------------------------------------------------------------------------------------------------------------------ local function on_game_load() if (caches_count > 0) then return end caches_exclude = { ["level_prefix_inventory_box_0002"] = true, ["level_prefix_inventory_box_0004"] = true, --pripyat, monolith ["level_prefix_inventory_box_0005"] = true, --pripyat, monolith ["level_prefix_inventory_box_0033"] = true, ["bar_arena_inventory_box"] = true, ["bar_arena_inventory_box_2"] = true, ["level_prefix_inventory_box"] = true, --bar ["level_prefix_inventory_box_0015"] = true, --bar ["level_prefix_inventory_box_0000"] = true, --cordon --bar ["level_prefix_inventory_box_0001"] = true, --bar ["esc_smart_terrain_2_12_box"] = true, ["yantar_treasure_11"] = true, ["mar_treasure_10"] = true, ["mar_smart_terrain_base_radio"] = true, ["level_prefix_inventory_box_0013"] = true, --wild territory ["agr_smart_terrain_1_6_n_2_box"] = true, ["agr_smart_terrain_1_6_box"] = true, ["zat_a2_actor_treasure"] = true, ["jup_b202_actor_treasure"] = true, ["mil_treasure_weapon_box"] = true, ["mil_smart_terrain_7_7_box"] = true, ["yan_smart_terrain_6_4_box"] = true, ["mil_smart_terrain_7_7_radio"] = true, ["labx8_inventory_box_0000"] = true, ["labx8_inventory_box_0001"] = true, ["warlab_inventory_box"] = true, ["val_recover_item_2_spawn"] = true, --darkvalley ["yan_actor_treasure"] = true, ["trc_inventory_box_0005"] = true, ["mil_q10_n"] = true } local sim = alife() for i=1, 65534 do local se_obj = sim:object(i) if (se_obj) then if (IsInvbox(nil,se_obj:clsid())) and not (caches_exclude[se_obj:name()]) then caches[se_obj.id] = false caches_count = caches_count + 1 end end end local easy_bonus = 2 for i=1, math.floor(caches_count/easy_bonus) do create_random_stash(true,"stash") end -- remove all unused stashes from the list --[[ for k,v in pairs(caches) do if (v == false) then caches[k] = nil end end --]] end local function actor_on_item_take_from_box(box,itm) if (DEV_DEBUG_DEV) then printf("actor took %s from %s",itm:name(),box:name()) end if (caches[box:id()] == true) then caches[box:id()] = false local hud = get_hud() if (hud and box:is_inv_box_empty()) then hud:HideActorMenu() end level.map_remove_object_spot(box:id(), "treasure") level.map_remove_object_spot(box:id(), "treasure_unique") level.map_remove_object_spot(box:id(), "treasure_reward") xr_statistic.inc_counter("stashes_found") local finded = xr_statistic.actor_statistic.stashes_found news_manager.send_treasure(1, finded) local box_name = box:name() -- printf("-"..box_name) end end local function save_state(data) --alun_utils.debug_write("coc_treasure_manager.save_state") if (caches_count <= 0) then return end if not (data.coc_treasure_manager) then data.coc_treasure_manager = {} end data.coc_treasure_manager.caches_count = caches_count data.coc_treasure_manager.caches = caches end local function load_state(data) if not (data.coc_treasure_manager) then return end caches_count = data.coc_treasure_manager.caches_count or caches_count caches = data.coc_treasure_manager.caches or caches data.coc_treasure_manager.caches_count = nil data.coc_treasure_manager.caches = nil end local function physic_object_on_use_callback(box,who) if (IsInvbox(box)) then try_spawn_treasure(box) end end ------------------------------------------------------------------------------------------------------------------ -- ON GAME START ------------------------------------------------------------------------------------------------------------------ function on_game_start() RegisterScriptCallback("on_game_load",on_game_load) RegisterScriptCallback("actor_on_item_take_from_box",actor_on_item_take_from_box) RegisterScriptCallback("save_state",save_state) RegisterScriptCallback("load_state",load_state) RegisterScriptCallback("physic_object_on_use_callback",physic_object_on_use_callback) end ------------------------------------------------------------------------------------------------------------------- last_secret = nil function create_random_stash(no_spot,hint,bonus_items) last_secret = nil if (caches_count <= 0) then return end last_secret = true local sim = alife() -- create a temporary table to use math.random local t = {} for id,v in pairs(caches) do -- false means box is available if (v == false) then table.insert(t,id) end end local index = #t > 0 and math.random(#t) if not (index) then return end local id = t[index] local se_box = id and sim:object(id) if not (se_box) then caches[id] = nil caches_count = caches_count - 1 return end if not (valid_item_list) then valid_item_list = get_valid_item_sections() end local spawned_item = bonus_items or {} local max_weight = math.random(80,130) if (has_alife_info("achieved_rag_and_bone")) then max_weight = max_weight + 50 end local function try_spawn_item(sec,min,max,chance,weight) if (max_weight >= weight) then local new_max = math.random(min,max) for i=1,new_max do if ((math.random(1,100)/100) <= chance) then table.insert(spawned_item,sec) max_weight = max_weight - weight end end end end local allow_weap = true local allow_armr = true -- iterate most expensive to least expensive or visa versa. Highest cost has highest weight. Consumables have higher max. Outfits have high weights. local functor = random_choice(function(t,a,b) return t[a] > t[b] end,function(t,a,b) return t[a] < t[b] end) for section,cost in spairs(valid_item_list,functor) do -- prevent a stash from having more than a single outfit or weapon local skip = false local bWeapon = itms_manager.is_weapon(section) local bOutfit = itms_manager.is_outfit(section) if not (skip) then -- decide for each section based on chance, weight and item classification if (cost >= 60000) then try_spawn_item(section,1,1,0.01,120) elseif (cost >= 35000) then try_spawn_item(section,1,1,0.02,110) elseif (cost >= 18000) then try_spawn_item(section,1,1,0.03,100) elseif (cost >= 9000) then try_spawn_item(section,1,1,0.05,85) elseif (cost >= 4500) then try_spawn_item(section,1,1,0.08,70) elseif (cost >= 2000) then try_spawn_item(section,1,1,0.10,60) elseif (cost >= 1000) then try_spawn_item(section,1,1,0.15,50) else try_spawn_item(section,1,1,0.25,40) end end if (max_weight <= 0) then break end end if (#spawned_item > 0) then -- create map spot if (no_spot ~= true and level.map_has_object_spot(id,"treasure") == 0 and level.map_has_object_spot(id,"treasure_unique") == 0 and level.map_has_object_spot(id,"treasure_reward") == 0) then if hint == "st_curious_stash" then level.map_add_object_spot_ser(id, "treasure_unique", hint or "") elseif hint == "st_reward_stash" then level.map_add_object_spot_ser(id, "treasure_reward", hint or "") else level.map_add_object_spot_ser(id, "treasure", hint or "") end local lvl = alife():level_name(game_graph():vertex(se_box.m_game_vertex_id):level_id()) news_manager.send_treasure(0, lvl) end caches[id] = table.concat(spawned_item,",") else caches[id] = false end end function try_spawn_treasure(box) local id = box:id() --printf("try_spawn_treasure [%s]",caches[id]) local easy_bonus = 0 if ((caches[id]) and (type(caches[id]) == "string")) then local spawned_item = alun_utils.str_explode(caches[id],",") caches[id] = true local sec,ammos,ct,ammo_type,ammo_section local ini = system_ini() local sim = alife() for i=1,#spawned_item do sec = spawned_item[i] if (sec ~= "" and ini:section_exist(sec)) then if (utils.is_ammo(sec)) then create_ammo(sec,box:position(),box:level_vertex_id(),box:game_vertex_id(),id,math.random(1,20)) else -- since we spawning on parent, we don't want to register object or packetdata will be inaccurate local se_obj = sim:create(sec,box:position(),0,0,id,false) if (se_obj) then local cls = se_obj:clsid() if (IsWeapon(nil,cls) and cls ~= clsid.wpn_knife_s) then --printf("coc_treasure_manager: item is fa") local data = stpk_utils.get_weapon_data(se_obj) if (data) then -- Create random addon flag for weapon flag = 0 if (ini:r_float_ex(sec,"scope_status")) then flag = flag + 1 end if (ini:r_bool_ex(sec, "block_sil_if_gl")) then local gl_or_sil = math.random(0,1) if (ini:r_float_ex(sec,"grenade_launcher_status") and gl_or_sil > 0) then flag = flag + 2 end if (ini:r_float_ex(sec,"silencer_status") and gl_or_sil < 1) then flag = flag + 4 end else if (ini:r_float_ex(sec,"grenade_launcher_status")) then flag = flag + 2 end if (ini:r_float_ex(sec,"silencer_status")) then flag = flag + 4 end end flag = math.random(0,flag) -- Create random ammo type ammos = alun_utils.parse_list(ini,sec,"ammo_class") ct = ammos and #ammos ammo_type = ammos and ct and math.random(0,ct-1) or 0 ammo_section = ammo_type and ammos[ammo_type+1] -- Weapons and Outfits data.addon_flags = flag data.ammo_type = 0 data.ammo_elapsed = 0 data.condition = math.random(45,80)/100 stpk_utils.set_weapon_data(data,se_obj) end elseif (IsOutfit(nil,cls) or IsHeadgear(nil,cls)) then local data = stpk_utils.get_item_data(se_obj) if (data) then data.condition = math.random(45,80)/100 stpk_utils.set_item_data(data,se_obj) end end -- because we didn't register, we do it now, manually alife():register(se_obj) end end else printf("coc_treasure_manager.script: invalid section %s",sec) end end end end function save(pk) if (USE_MARSHAL) then return end pk:w_u16(caches_count) for id,v in pairs(caches) do pk:w_u16(id) pk:w_bool(v) end end function load(pk) if (USE_MARSHAL) then return end caches_count = pk:r_u16() for i=1,caches_count do caches[pk:r_u16()] = pk:r_bool() end end function get_valid_item_sections() local t = {} local ini = system_ini() local ltx = ltx_base ini:section_for_each(function(section) if not (ltx:line_exist("ignore_sections",section)) then if (ini:line_exist(section,"cform")) then if (ini:r_bool_ex(section,"can_trade",true) == true) then if (ini:r_bool_ex(section,"quest_item",false) == false) then if not (string.find(section,"mp_")) then local name = ini:r_string_ex(section,"inv_name") if (name and name ~= "" and name ~= "default") then local cost = ini:r_float_ex(section,"cost") or 0 if (cost > 0) then t[section] = cost end end end end end end end end ) -- List of all items in game that are not quest items if (DEV_DEBUG_DEV) then local cfg = io.open("valid_item_sections.ltx","w+") for k,v in pairs(t) do cfg:write(k.."\n") end cfg:close() end return t end