local corpses = {} local initial_inv = {} local cur_npc_id = nil local sfind = string.find local snd_t = { ["w_ammo"] = { "ammo_1", "ammo_2", "ammo_3", "ammo_4" }, ["w_misc"] = { "parts_1", "parts_2", "parts_3" }, ["w_explosive"] = { "grenade_1", "grenade_2" }, ["w_melee"] = { "knife_1", "knife_2" }, ["wpn"] = { "wpn_1", "wpn_2" }, ["o_helmet"] = { "cloth_1" }, ["outfit"] = { "cloth_2", "cloth_3" }, ["i_arty"] = { "cloth_1", "cloth_2" }, ["i_arty_cont"] = { "wpnbig_1", "wpnbig_2" }, ["i_arty_junk"] = { "cloth_1", "cloth_2" }, ["i_attach"] = { "parts_1", "parts_2", "parts_3" }, ["i_drink"] = { "bottle_1", "bottle_2", "bottle_3", "bottle_4" }, ["i_medical"] = { "pills_1", "pills_2" }, ["i_tool"] = { "take_all" }, ["i_device"] = { "parts_1", "parts_2" }, ["i_letter"] = { "money_paper" }, ["i_part"] = { "parts_1", "parts_2", "parts_3" }, ["money_paper"] = { "money_paper" }, ["money_coin"] = { "money_coin_1", "money_coin_2" }, ["generic"] = { "generic_3", "generic_4", "generic_5", "parts_2" }, -- i_upgrade, i_repair, i_kit, i_food, i_mutant_cooked, i_mutant_raw, i_mutant_part, i_mutant_belt, i_backpack } local replace_snd_by_sec = { ["money_paper"] = { "cash", "money" }, ["money_coin"] = { "roubles" }, ["generic"] = { "joint", "marijuana", "cigarettes", "cigar", "tobacco", "drug_booster", "bandage", "jgut", "stimpack", "rebirth", "tetanus", "glucose", "cocaine", "salicidic_acid", "morphine", "adrenalin" }, } local replace_snd_by_kind = { ["wpn"] = { "w_rifle", "w_pistol", "w_smg", "w_shotgun", "w_sniper" }, ["outfit"] = { "o_heavy", "o_light", "o_medium", "o_sci" }, } -- mcm local time_min = loot_searching_mcm.get_config("time_min") local time_max = loot_searching_mcm.get_config("time_max") local hud_enable = loot_searching_mcm.get_config("hud_enable") local sound_enable = loot_searching_mcm.get_config("sound_enable") local sound_enable_vol = loot_searching_mcm.get_config("sound_enable_vol") local sound_background_enable = loot_searching_mcm.get_config("sound_bg_enable") local sound_background_enable_vol = loot_searching_mcm.get_config("sound_bg_enable_vol") local hud_icon = loot_searching_mcm.get_config("hud_icon") local debugx = loot_searching_mcm.get_config("debugx") -------- function pr(...) if not debugx then return end printf(...) end function GUI_on_show() CreateTimeEvent("init_inv_ev", "init_inv_ac", 0, save_initial_inv) -- has to be on next update end function save_initial_inv() local inventory = ui_inventory.GUI if not inventory then return true end local npc = inventory:GetPartner() or nil local npc_id = npc and npc:id() or nil if inventory.mode ~= "loot" or not npc_id then return true end if not IsStalker(npc) or IsInvbox(npc) then return true end if not initial_inv[npc_id] then initial_inv[npc_id] = {} corpses[npc_id] = {} local function initial_items(owner, obj) if not initial_inv[npc_id][obj:id()] then pr("$ GUI ON SHOW npc item: [ %s ]", obj:id()) initial_inv[npc_id][obj:id()] = true end end npc:iterate_inventory(initial_items, npc) npc:iterate_ruck(initial_items, npc) end cur_npc_id = npc_id -- for hud return true end local basePI = ui_inventory.UIInventory.ParseInventory function ui_inventory.UIInventory:ParseInventory(npc, all, id_list, ignore_kind) local inv = basePI(self, npc, all, id_list, ignore_kind) if not (self:IsInvOwner(npc) and npc:id() ~= AC_ID and self.mode == "loot" and all and id_list) then return inv end local res = dup_table(inv) -- corpses == (nil = timer for item does not exist, true = timer ticking, false = timer ended) local npc_id = npc:id() for item_id, item in pairs(res) do local item_timer = show_item_time(item_id) -- if item was in initial spawn and item isn't on timer if initial_inv[npc_id] and initial_inv[npc_id][item_id] and corpses[npc_id] then -- create timer if it does not exist if corpses[npc_id][item_id] == nil then corpses[npc_id][item_id] = true pr("~ id_list || Creating Time Event for npc_id: [ %s ] || itm_sec: [ %s ] || itm_id: [ %s ] || itm_tmr: [ %s ]", npc_id, item:section(), item_id, item_timer) CreateTimeEvent("item_hiding", "item_hiding_" .. npc_id .. "_" .. item_id, item_timer, show_item, npc_id, item_id) end -- and pass table without this item if timer didn't exist or still ticking (guess just true works) if corpses[npc_id][item_id] ~= false then res[item_id] = nil end end end return res end function show_item_time(id) local item = level.object_by_id(id) local sec = item:section() local weight = SYS_GetParam(2, sec, "inv_weight", 2) weight = weight > 0.05 and weight or 0.05 local grid = SYS_GetParam(2, sec, "inv_grid_width", 1) * SYS_GetParam(2, sec, "inv_grid_height", 1) -- size of item xd local time = math.random(time_min, time_max) - weight - grid time = time > 1 and time or 1 return time end function show_item(npc_id, id) local gui = GetActorMenu() if corpses[npc_id] and corpses[npc_id][id] then pr("- Show at npc: [ %s ] item: [ %s ]", npc_id, id) item_found_snd(id) corpses[npc_id][id] = false if actor_menu.get_last_mode() == 4 and gui.npc_id == npc_id then --if loot gui not open or is open to a diff NPC don't refresh. --Sorting plus fix if zzz_rax_sortingplus_mcm and zzz_rax_sortingplus_mcm.NPCINV then ui_inventory.GUI.CC["npc_bag"]:Reset() end gui:UpdateInventories() end end return true end function item_found_snd(id) if not sound_enable then return end local item = level.object_by_id(id) local sec = item and item:section() or nil if not sec then return end local kind = SYS_GetParam(0, sec, "kind", "generic") -- generalize some kinds local skip_sfind = false for new_kind, t in pairs(replace_snd_by_kind) do for i = 1, #t do if t[i] == kind then kind = new_kind skip_sfind = true break end end end -- replace some kinds by section string find if not skip_sfind then for new_kind, t in pairs(replace_snd_by_sec) do for i = 1, #t do if sfind(sec, t[i]) then kind = new_kind break end end end end local snd_kind = snd_t[kind] or snd_t["generic"] local random_kind_snd = snd_kind[math.random(1, #snd_kind)] local snd_file = "interface\\items\\inv_items_" .. random_kind_snd local snd = sound_object(snd_file) if snd then snd:play_at_pos(db.actor, VEC_ZERO, 0, sound_object.s2d) snd.volume = snd.volume * sound_enable_vol else pr("! sound: [ %s ] not found", snd_file) end end function GUI_on_hide() local inventory = ui_inventory.GUI if not inventory then return end local npc = inventory:GetPartner() or nil local npc_id = npc and npc:id() or nil if inventory.mode ~= "loot" then return end if not npc_id then return end -- check if val is true (timer still ticking) when we close loot window for npc_id, t in pairs(corpses) do for item_id, val in pairs(t) do if val == true then pr("! Removing time event for npc: [ %s ] and item: [ %s ]", npc_id, item_id) RemoveTimeEvent("item_hiding", "item_hiding_" .. npc_id .. "_" .. item_id) corpses[npc_id][item_id] = nil -- start timer again next time we loot, if item didnt appear and we have closed the window before end end end cur_npc_id = nil -- for hud end -- HUD function ui_inventory.UIInventory:InitIconX() self.loot_xml = CScriptXmlInit() self.loot_xml:ParseFile("ui_xcvb_looting.xml") if hud_icon == 1 then self.loot_icon_x = self.loot_xml:InitTextWnd("looting_text", self) self.loot_icon_x:SetText(game.translate_string("st_xcvb_looting")) elseif hud_icon == 2 then self.loot_icon_x = self.loot_xml:InitStatic("poarch_cringe_seq", self) end end local base = ui_inventory.UIInventory.UpdateInfo function ui_inventory.UIInventory:UpdateInfo(go) self:LootUpdateX() base(self, go) end local hud_tmr local snd_tmr = 0 local dots = 0 function ui_inventory.UIInventory:LootUpdateX() local tg = time_global() if (hud_tmr and tg < hud_tmr) then return end hud_tmr = tg + 500 if not self.loot_icon_x then self:InitIconX() end if not cur_npc_id then self.loot_icon_x:Show(false) return end if not hud_enable then self.loot_icon_x:Show(false) return end local itms = 0 -- numeric instead of boolean in case of percents or bars someday for npc_id, t in pairs(corpses) do if npc_id == cur_npc_id then for item_id, val in pairs(t) do if val then itms = itms + 1 end end end end if itms <= 0 then self.loot_icon_x:Show(false) return end local function dots_str() local str = game.translate_string("st_xcvb_looting") dots = dots < 5 and dots + 1 or 1 for i = 1, dots do str = str .. " ." end return str end -- looting sound if sound_background_enable and tg > snd_tmr then local snd_file = "looting\\looting_" .. math.random(1, 12) local snd = sound_object(snd_file) if snd then snd_tmr = tg + snd:length() * 0.5 pr("new sound: %s || delay_by: %s", snd_file, snd:length() * 0.5) snd:play_at_pos(db.actor, VEC_ZERO, 0, sound_object.s2d) snd.volume = snd.volume * sound_background_enable_vol end end --------- self.loot_icon_x:Show(true) if hud_icon == 1 then self.loot_icon_x:SetText(dots_str()) end end ---------------------- function server_entity_on_unregister(se_obj, typ) local id = se_obj.id corpses[id] = nil initial_inv[id] = nil end function on_option_change() time_min = loot_searching_mcm.get_config("time_min") time_max = loot_searching_mcm.get_config("time_max") hud_enable = loot_searching_mcm.get_config("hud_enable") sound_enable = loot_searching_mcm.get_config("sound_enable") sound_enable_vol = loot_searching_mcm.get_config("sound_enable_vol") sound_background_enable = loot_searching_mcm.get_config("sound_bg_enable") sound_background_enable_vol = loot_searching_mcm.get_config("sound_bg_enable_vol") debugx = loot_searching_mcm.get_config("debugx") end function on_game_start() RegisterScriptCallback("GUI_on_show", GUI_on_show) RegisterScriptCallback("GUI_on_hide", GUI_on_hide) RegisterScriptCallback("server_entity_on_unregister", server_entity_on_unregister) RegisterScriptCallback("on_option_change", on_option_change) end