From 85baf51113e91afcc6e1c5c75413574704b9175a Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 23 Jan 2026 23:23:40 +0000 Subject: [PATCH 1/4] Implement dynamic loot pools and expanded categories for Tarkov loot system - Expose `GetAllTarkovItems` in `sh_tarkov_inventory.lua` to allow server access to item registry. - Replace hardcoded `LOOT_POOLS` in `sv_tarkov_loot_bridge.lua` with dynamic generation based on item types (Weapons, Ammo, Gear, Medical, Entities, etc.). - Fix syntax error in `hook.Add` and typo in `LOO_POOLS` in `sv_tarkov_loot_bridge.lua`. - Update `tarkov_loot.lua` tool to include buttons for new categories: Ammo, Gear, Entities. --- .../autorun/server/sv_tarkov_loot_bridge.lua | 86 ++++++++++++++++--- .../lua/autorun/sh_tarkov_inventory.lua | 4 + .../weapons/gmod_tool/stools/tarkov_loot.lua | 3 + 3 files changed, 83 insertions(+), 10 deletions(-) diff --git a/Escape from GMOD utils/lua/autorun/server/sv_tarkov_loot_bridge.lua b/Escape from GMOD utils/lua/autorun/server/sv_tarkov_loot_bridge.lua index cd3c34f..7421bfc 100644 --- a/Escape from GMOD utils/lua/autorun/server/sv_tarkov_loot_bridge.lua +++ b/Escape from GMOD utils/lua/autorun/server/sv_tarkov_loot_bridge.lua @@ -20,24 +20,90 @@ local CACHE_ENTITIES = { } -- LOOT TABLES (Simple list of item IDs from sh_tarkov_inventory.lua) --- You can expand this list with more specific items -local LOOT_POOLS = { - ["weapons"] = {"weapon_pistol", "weapon_smg1", "medkit"}, - ["medical"] = {"medkit", "medkit", "tushonka"}, - ["misc"] = {"scrap", "tushonka", "backpack_scav"}, - ["rare"] = {"bitcoin", "weapon_smg1", "rig_combine"}, - ["random"] = {"tushonka", "medkit", "bitcoin", "weapon_pistol", "backpack_scav", "scrap", "rig_combine"} -} +-- LOOT TABLES (Dynamic now) +local LOOT_POOLS = {} + +local function BuildLootPools() + LOOT_POOLS = { + ["weapons"] = {}, + ["medical"] = {}, + ["misc"] = {}, + ["rare"] = {}, + ["random"] = {}, + ["ammo"] = {}, + ["entities"] = {}, + ["gear"] = {} + } + + if not GetAllTarkovItems then return end + local items = GetAllTarkovItems() + + for id, data in pairs(items) do + -- Add to Random + table.insert(LOOT_POOLS["random"], id) + + -- Determine Category + local name = string.lower(data.Name or "") + local desc = string.lower(data.Desc or "") + local type = data.Type + local slot = data.Slot + + local isWeapon = (type == "equip" and (slot == "Primary" or slot == "Secondary")) + local isGear = (type == "equip" and (slot == "Backpack" or slot == "Rig" or slot == "Armor")) + local isAmmo = (string.find(name, "ammo") or string.find(desc, "ammo") or string.find(name, "round") or string.find(desc, "cartridge") or string.find(desc, "magazine")) + local isMedical = (string.find(name, "med") or string.find(name, "health") or string.find(name, "heal") or string.find(desc, "heal") or id == "tushonka") -- Tushonka is food but keeps you alive :P + + -- Special handling for "Category" field if it was captured from entity registry + local cat = string.lower(data.Desc or "") -- In sh_tarkov_inventory, Desc often contains "Category: ..." + if string.find(cat, "ammo") then isAmmo = true end + + if isWeapon then + table.insert(LOOT_POOLS["weapons"], id) + elseif isAmmo then + table.insert(LOOT_POOLS["ammo"], id) + elseif isMedical then + table.insert(LOOT_POOLS["medical"], id) + elseif isGear then + table.insert(LOOT_POOLS["gear"], id) + elseif type == "item" then + -- If it's an item and not ammo/med, it's likely an entity or misc + -- If it was registered from scripted_ents and not handled above + if not isAmmo and not isMedical then + table.insert(LOOT_POOLS["entities"], id) + table.insert(LOOT_POOLS["misc"], id) -- Also put in misc + end + else + table.insert(LOOT_POOLS["misc"], id) + end + + -- Rare check (Simple keyword search) + if string.find(name, "rare") or string.find(desc, "valuable") or id == "bitcoin" or id == "armor_hev" then + table.insert(LOOT_POOLS["rare"], id) + end + end + + -- Fallbacks if empty + if #LOOT_POOLS["weapons"] == 0 then table.insert(LOOT_POOLS["weapons"], "weapon_pistol") end + if #LOOT_POOLS["random"] == 0 then table.insert(LOOT_POOLS["random"], "tushonka") end + + print("[Tarkov Loot] Generated Loot Pools. Total Items: " .. table.Count(items)) +end + +hook.Add("InitPostEntity", "TarkovBuildLootPools", function() + -- Run after a short delay to ensure all items are registered + timer.Simple(1, BuildLootPools) +end) -- Helper to get random item from pool local function GetRandomItem(poolName) - local pool = LOOT_POOLS[poolName] or LOO_POOLS["random"] + local pool = LOOT_POOLS[poolName] or LOOT_POOLS["random"] + if not pool or #pool == 0 then return "tushonka" end return pool[math.random(#pool)] end -- HOOK: PlayerUse -- Intercepts the use key on loot entities -hook.Add("PlayerUse","TarkovBridge_Use"), function(ply, ent) +hook.Add("PlayerUse", "TarkovBridge_Use", function(ply, ent) if not IsValid(ent) then return end local class = ent:GetClass() diff --git a/Escape from GMOD utils/lua/autorun/sh_tarkov_inventory.lua b/Escape from GMOD utils/lua/autorun/sh_tarkov_inventory.lua index 8cc1920..a5a899f 100644 --- a/Escape from GMOD utils/lua/autorun/sh_tarkov_inventory.lua +++ b/Escape from GMOD utils/lua/autorun/sh_tarkov_inventory.lua @@ -23,6 +23,10 @@ end function GetItemData(id) return ITEMS[id] end + +function GetAllTarkovItems() + return ITEMS +end -- --- ITEM DEFINITIONS --- diff --git a/Escape from GMOD utils/lua/weapons/gmod_tool/stools/tarkov_loot.lua b/Escape from GMOD utils/lua/weapons/gmod_tool/stools/tarkov_loot.lua index 5baaae3..d0a1a3e 100644 --- a/Escape from GMOD utils/lua/weapons/gmod_tool/stools/tarkov_loot.lua +++ b/Escape from GMOD utils/lua/weapons/gmod_tool/stools/tarkov_loot.lua @@ -111,7 +111,10 @@ function TOOL.BuildCPanel(panel) AddTagButton("random") AddTagButton("weapons") + AddTagButton("ammo") + AddTagButton("gear") AddTagButton("medical") + AddTagButton("entities") AddTagButton("misc") AddTagButton("rare") From 29dbd7a4ca6544305d5f84e68e095408127801b7 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 23 Jan 2026 23:29:45 +0000 Subject: [PATCH 2/4] Implement dynamic loot pools and expanded categories for Tarkov loot system - Expose `GetAllTarkovItems` in `sh_tarkov_inventory.lua`. - Replace hardcoded `LOOT_POOLS` in `sv_tarkov_loot_bridge.lua` with dynamic generation logic. - Fix syntax errors (hook arguments, typos) in `sv_tarkov_loot_bridge.lua`. - Update `tarkov_loot.lua` tool with buttons for new categories (Ammo, Gear, Entities). --- .../lua/autorun/server/sv_tarkov_loot_bridge.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Escape from GMOD utils/lua/autorun/server/sv_tarkov_loot_bridge.lua b/Escape from GMOD utils/lua/autorun/server/sv_tarkov_loot_bridge.lua index 7421bfc..af0a249 100644 --- a/Escape from GMOD utils/lua/autorun/server/sv_tarkov_loot_bridge.lua +++ b/Escape from GMOD utils/lua/autorun/server/sv_tarkov_loot_bridge.lua @@ -200,4 +200,4 @@ hook.Add("PlayerUse", "TarkovBridge_Use", function(ply, ent) -- Return false to BLOCK the entity's default behavior -- (e.g. stop the workshop addon from opening its own menu) return false - end + end) From 9b70364a665a350d310dc0df428be144e3baa885 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 18:21:39 +0000 Subject: [PATCH 3/4] Implement dynamic loot pools, close-with-E, and persistence fix - **Dynamic Loot Pools**: Replace hardcoded loot lists with `BuildLootPools`, which auto-categorizes items (Weapons, Ammo, Gear, Medical, Entities) from registered addons. - **Tool Gun Update**: Add buttons for new categories (Ammo, Gear, Entities) to `tarkov_loot.lua`. - **Close with E**: Players can now close an open loot container by pressing 'Use' again (with a 0.5s spam delay). - **Persistence**: Loot containers now correctly remember if a player has searched them, allowing instant re-opening. - **Fixes**: Corrected Lua syntax error in `sv_tarkov_loot_bridge.lua` and ensured `GetAllTarkovItems` is exposed. --- .../autorun/server/sv_tarkov_loot_bridge.lua | 293 ++++++++++-------- 1 file changed, 166 insertions(+), 127 deletions(-) diff --git a/Escape from GMOD utils/lua/autorun/server/sv_tarkov_loot_bridge.lua b/Escape from GMOD utils/lua/autorun/server/sv_tarkov_loot_bridge.lua index af0a249..70f30ea 100644 --- a/Escape from GMOD utils/lua/autorun/server/sv_tarkov_loot_bridge.lua +++ b/Escape from GMOD utils/lua/autorun/server/sv_tarkov_loot_bridge.lua @@ -1,25 +1,25 @@ --- SV_TARKOV_LOOT_BRIDGE.LUA --- Connects Workshop Loot Entities to our Custom Inventory System - -local TAG = "TarkovInv" - --- A cache to avoid expensive string.find operations on entity classes -local CLASS_CACHE = {} - --- Ensure Network Strings exist (Redundant safety check to prevent "unpooled message" errors) -util.AddNetworkString(TAG .. "_SearchUI") -util.AddNetworkString(TAG .. "_Update") - --- CONFIG: Add any entity classes that should act as loot containers here -local CACHE_ENTITIES = { - ["ent_loot_cache"] = true, - ["item_item_crate"] = true, - ["sent_lootbox"] = true, - ["sim_loot_crate"] = true, - ["ent_loot_cache_tarkov"] = true -- Added our custom test entity just in case -} - --- LOOT TABLES (Simple list of item IDs from sh_tarkov_inventory.lua) +-- SV_TARKOV_LOOT_BRIDGE.LUA +-- Connects Workshop Loot Entities to our Custom Inventory System + +local TAG = "TarkovInv" + +-- A cache to avoid expensive string.find operations on entity classes +local CLASS_CACHE = {} + +-- Ensure Network Strings exist (Redundant safety check to prevent "unpooled message" errors) +util.AddNetworkString(TAG .. "_SearchUI") +util.AddNetworkString(TAG .. "_Update") + +-- CONFIG: Add any entity classes that should act as loot containers here +local CACHE_ENTITIES = { + ["ent_loot_cache"] = true, + ["item_item_crate"] = true, + ["sent_lootbox"] = true, + ["sim_loot_crate"] = true, + ["ent_loot_cache_tarkov"] = true -- Added our custom test entity just in case +} + +-- LOOT TABLES (Simple list of item IDs from sh_tarkov_inventory.lua) -- LOOT TABLES (Dynamic now) local LOOT_POOLS = {} @@ -93,111 +93,150 @@ hook.Add("InitPostEntity", "TarkovBuildLootPools", function() -- Run after a short delay to ensure all items are registered timer.Simple(1, BuildLootPools) end) - --- Helper to get random item from pool -local function GetRandomItem(poolName) + +-- Helper to get random item from pool +local function GetRandomItem(poolName) local pool = LOOT_POOLS[poolName] or LOOT_POOLS["random"] if not pool or #pool == 0 then return "tushonka" end - return pool[math.random(#pool)] -end - --- HOOK: PlayerUse --- Intercepts the use key on loot entities + return pool[math.random(#pool)] +end + +-- HOOK: PlayerUse +-- Intercepts the use key on loot entities hook.Add("PlayerUse", "TarkovBridge_Use", function(ply, ent) - if not IsValid(ent) then return end - - local class = ent:GetClass() - - -- Check cache first - if CLASS_CACHE[class] == false then return end - if CLASS_CACHE[class] == true then - -- This is a known loot box, proceed with logic - else - -- Not in cache, perform the expensive check - local isLoot = CACHE_ENTITIES[class] or string.find(class, "loot") or string.find(class, "cache") - if isLoot then - CLASS_CACHE[class] = true -- Store positive result - else - CLASS_CACHE[class] = false -- Store negative result - return - end - end - - -- This code block will only be reached if the entity is a loot container - -- Safety: If searching flag got stuck but timer is gone, reset it - if ply.IsSearching and (ply.SearchEndTime or 0) < CurTime() then - ply.IsSearching = false - end - - -- Prevent spam / check if already searching - if ply.IsSearching then return false end - - -- Get the pool tag set by your Admin Tool - local poolTag = ent:GetNWString("LootPool", "random") - -- print("[Tarkov Bridge] Found Loot Box! Pool: " .. poolTag) - - -- 1. START SEARCHING (Visuals) - ply.IsSearching = true - ply.SearchEndTime = CurTime() + 3.5 -- Safety timeout - - ply:EmitSound("physics/cardboard/cardboard_box_impact_soft2.wav") - - -- Send Search Progress Bar to Client - net.Start(TAG .. "_SearchUI") - net.WriteFloat(3.0) -- 3.0 Seconds duration - net.Send(ply) - - -- 2. TIMER (Logic) - timer.Create("TarkovSearch_" .. ply:SteamID64(), 3.0, 1, function() - if not IsValid(ply) then return end - ply.IsSearching = false - - if not IsValid(ent) then return end - - -- Validate Distance - if ply:GetPos():DistToSqr(ent:GetPos()) > 150*150 then - ply:ChatPrint("You moved too far away.") - return - end - - -- 3. GENERATE LOOT (Only if not already looted/generated) - if not ent.CacheInventory then - ent.CacheInventory = {} - - -- Generate 3-8 items based on the tag - for i=1, math.random(3, 8) do - local slot = math.random(1, 20) -- 20 is cache size - local item = GetRandomItem(poolTag) - - if not ent.CacheInventory[slot] then - ent.CacheInventory[slot] = item - end - end - -- print("[Tarkov Bridge] Generated loot for box.") - end - - -- 4. OPEN INVENTORY MENU - -- Set this entity as the player's active cache session - ply.ActiveLootCache = ent - - -- Sync the cache data to the player's "cache" container slot in their session - if ply.TarkovData then - ply.TarkovData.Containers.cache = table.Copy(ent.CacheInventory) - - -- Send update to client (This opens the menu because IsCacheOpen will be true) - net.Start(TAG .. "_Update") - net.WriteTable(ply.TarkovData) - net.WriteBool(true) -- Tell client cache is OPEN - net.Send(ply) - - ply:EmitSound("items/ammo_pickup.wav") - - -- Force open menu command just in case - ply:ConCommand("tarkov_open_inventory") - end - end) - - -- Return false to BLOCK the entity's default behavior - -- (e.g. stop the workshop addon from opening its own menu) - return false - end) + if not IsValid(ent) then return end + + local class = ent:GetClass() + + -- Check cache first + if CLASS_CACHE[class] == false then return end + if CLASS_CACHE[class] == true then + -- This is a known loot box, proceed with logic + else + -- Not in cache, perform the expensive check + local isLoot = CACHE_ENTITIES[class] or string.find(class, "loot") or string.find(class, "cache") + if isLoot then + CLASS_CACHE[class] = true -- Store positive result + else + CLASS_CACHE[class] = false -- Store negative result + return + end + end + + -- This code block will only be reached if the entity is a loot container + + -- CLOSE LOGIC: If already open, close it (with delay) + if ply.ActiveLootCache == ent then + if (ply.LootOpenTime and CurTime() > ply.LootOpenTime + 0.5) then + ply.ActiveLootCache = nil + net.Start(TAG .. "_Update") + net.WriteTable(ply.TarkovData) + net.WriteBool(false) + net.Send(ply) + ply:EmitSound("items/ammo_pickup.wav") + end + return false + end + + -- Safety: If searching flag got stuck but timer is gone, reset it + if ply.IsSearching and (ply.SearchEndTime or 0) < CurTime() then + ply.IsSearching = false + end + + -- Prevent spam / check if already searching + if ply.IsSearching then return false end + + -- Get the pool tag set by your Admin Tool + local poolTag = ent:GetNWString("LootPool", "random") + + -- Helper to Ensure Loot Exists + local function EnsureLoot() + if not ent.CacheInventory then + ent.CacheInventory = {} + -- Generate 3-8 items based on the tag + for i=1, math.random(3, 8) do + local slot = math.random(1, 20) + local item = GetRandomItem(poolTag) + if not ent.CacheInventory[slot] then + ent.CacheInventory[slot] = item + end + end + end + end + + -- PERSISTENCE: Check if already searched + if ply.SearchedCaches and ply.SearchedCaches[ent:EntIndex()] then + EnsureLoot() + + ply.ActiveLootCache = ent + ply.LootOpenTime = CurTime() + + if ply.TarkovData then + ply.TarkovData.Containers.cache = table.Copy(ent.CacheInventory) + net.Start(TAG .. "_Update") + net.WriteTable(ply.TarkovData) + net.WriteBool(true) + net.Send(ply) + ply:EmitSound("items/ammo_pickup.wav") + ply:ConCommand("tarkov_open_inventory") + end + return false + end + + -- 1. START SEARCHING (Visuals) + ply.IsSearching = true + ply.SearchEndTime = CurTime() + 3.5 -- Safety timeout + + ply:EmitSound("physics/cardboard/cardboard_box_impact_soft2.wav") + + -- Send Search Progress Bar to Client + net.Start(TAG .. "_SearchUI") + net.WriteFloat(3.0) -- 3.0 Seconds duration + net.Send(ply) + + -- 2. TIMER (Logic) + timer.Create("TarkovSearch_" .. ply:SteamID64(), 3.0, 1, function() + if not IsValid(ply) then return end + ply.IsSearching = false + + if not IsValid(ent) then return end + + -- Validate Distance + if ply:GetPos():DistToSqr(ent:GetPos()) > 150*150 then + ply:ChatPrint("You moved too far away.") + return + end + + -- 3. GENERATE LOOT + EnsureLoot() + + -- 4. OPEN INVENTORY MENU + -- Set this entity as the player's active cache session + ply.ActiveLootCache = ent + ply.LootOpenTime = CurTime() + + -- Mark as searched + if not ply.SearchedCaches then ply.SearchedCaches = {} end + ply.SearchedCaches[ent:EntIndex()] = true + + -- Sync the cache data to the player's "cache" container slot in their session + if ply.TarkovData then + ply.TarkovData.Containers.cache = table.Copy(ent.CacheInventory) + + -- Send update to client (This opens the menu because IsCacheOpen will be true) + net.Start(TAG .. "_Update") + net.WriteTable(ply.TarkovData) + net.WriteBool(true) -- Tell client cache is OPEN + net.Send(ply) + + ply:EmitSound("items/ammo_pickup.wav") + + -- Force open menu command just in case + ply:ConCommand("tarkov_open_inventory") + end + end) + + -- Return false to BLOCK the entity's default behavior + -- (e.g. stop the workshop addon from opening its own menu) + return false + end) \ No newline at end of file From 9e9ec05eda82a10f44adbc4fb6c86176ad0e3750 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 18:35:02 +0000 Subject: [PATCH 4/4] Fix loot interactions, dynamic pools, and ctrl+click shortcuts - **Loot Logic**: Implemented dynamic item categorization (weapons, ammo, gear, etc.) from registered addons. - **Controls**: Restored `Ctrl+Left Click` for quick-moving items (Equip/Loot/Store). - **QoL**: Implemented "Press E to Close" with spam prevention delay. - **Bug Fix**: Fixed individual items acting as loot crates by refining entity class checks. - **Bug Fix**: Added `Think` hook to auto-close inventory when moving away from the cache. - **Tool**: Updated Admin Tool with new category buttons. --- .../autorun/server/sv_tarkov_loot_bridge.lua | 217 ++++++++++-------- .../lua/autorun/sh_tarkov_inventory.lua | 61 +++++ 2 files changed, 184 insertions(+), 94 deletions(-) diff --git a/Escape from GMOD utils/lua/autorun/server/sv_tarkov_loot_bridge.lua b/Escape from GMOD utils/lua/autorun/server/sv_tarkov_loot_bridge.lua index 70f30ea..ca38577 100644 --- a/Escape from GMOD utils/lua/autorun/server/sv_tarkov_loot_bridge.lua +++ b/Escape from GMOD utils/lua/autorun/server/sv_tarkov_loot_bridge.lua @@ -11,15 +11,15 @@ util.AddNetworkString(TAG .. "_SearchUI") util.AddNetworkString(TAG .. "_Update") -- CONFIG: Add any entity classes that should act as loot containers here +-- FIX: Only include actual containers, exclude individual items local CACHE_ENTITIES = { ["ent_loot_cache"] = true, ["item_item_crate"] = true, ["sent_lootbox"] = true, ["sim_loot_crate"] = true, - ["ent_loot_cache_tarkov"] = true -- Added our custom test entity just in case + ["ent_loot_cache_tarkov"] = true } --- LOOT TABLES (Simple list of item IDs from sh_tarkov_inventory.lua) -- LOOT TABLES (Dynamic now) local LOOT_POOLS = {} @@ -106,6 +106,10 @@ end hook.Add("PlayerUse", "TarkovBridge_Use", function(ply, ent) if not IsValid(ent) then return end + -- FIX: Ignore individual pickup items (ent_loot_item) + -- They should use their own Use logic (manual pickup) or the trace pickup system + if ent.IsTarkovLootItem then return end + local class = ent:GetClass() -- Check cache first @@ -114,7 +118,8 @@ hook.Add("PlayerUse", "TarkovBridge_Use", function(ply, ent) -- This is a known loot box, proceed with logic else -- Not in cache, perform the expensive check - local isLoot = CACHE_ENTITIES[class] or string.find(class, "loot") or string.find(class, "cache") + -- FIX: Be more strict. Don't just match "loot" which catches "ent_loot_item" + local isLoot = CACHE_ENTITIES[class] or (string.find(class, "loot") and not string.find(class, "item")) or string.find(class, "cache") if isLoot then CLASS_CACHE[class] = true -- Store positive result else @@ -125,118 +130,142 @@ hook.Add("PlayerUse", "TarkovBridge_Use", function(ply, ent) -- This code block will only be reached if the entity is a loot container - -- CLOSE LOGIC: If already open, close it (with delay) - if ply.ActiveLootCache == ent then - if (ply.LootOpenTime and CurTime() > ply.LootOpenTime + 0.5) then - ply.ActiveLootCache = nil - net.Start(TAG .. "_Update") - net.WriteTable(ply.TarkovData) - net.WriteBool(false) - net.Send(ply) - ply:EmitSound("items/ammo_pickup.wav") - end - return false + -- CLOSE LOGIC: If already open, close it (with delay) + if ply.ActiveLootCache == ent then + if (ply.LootOpenTime and CurTime() > ply.LootOpenTime + 0.5) then + ply.ActiveLootCache = nil + net.Start(TAG .. "_Update") + net.WriteTable(ply.TarkovData) + net.WriteBool(false) + net.Send(ply) + ply:EmitSound("items/ammo_pickup.wav") end + return false + end - -- Safety: If searching flag got stuck but timer is gone, reset it - if ply.IsSearching and (ply.SearchEndTime or 0) < CurTime() then - ply.IsSearching = false - end + -- Safety: If searching flag got stuck but timer is gone, reset it + if ply.IsSearching and (ply.SearchEndTime or 0) < CurTime() then + ply.IsSearching = false + end - -- Prevent spam / check if already searching - if ply.IsSearching then return false end - - -- Get the pool tag set by your Admin Tool - local poolTag = ent:GetNWString("LootPool", "random") - - -- Helper to Ensure Loot Exists - local function EnsureLoot() - if not ent.CacheInventory then - ent.CacheInventory = {} - -- Generate 3-8 items based on the tag - for i=1, math.random(3, 8) do - local slot = math.random(1, 20) - local item = GetRandomItem(poolTag) - if not ent.CacheInventory[slot] then - ent.CacheInventory[slot] = item - end + -- Prevent spam / check if already searching + if ply.IsSearching then return false end + + -- Get the pool tag set by your Admin Tool + local poolTag = ent:GetNWString("LootPool", "random") + + -- Helper to Ensure Loot Exists + local function EnsureLoot() + if not ent.CacheInventory then + ent.CacheInventory = {} + -- Generate 3-8 items based on the tag + for i=1, math.random(3, 8) do + local slot = math.random(1, 20) + local item = GetRandomItem(poolTag) + if not ent.CacheInventory[slot] then + ent.CacheInventory[slot] = item end end end + end - -- PERSISTENCE: Check if already searched - if ply.SearchedCaches and ply.SearchedCaches[ent:EntIndex()] then - EnsureLoot() + -- PERSISTENCE: Check if already searched + if ply.SearchedCaches and ply.SearchedCaches[ent:EntIndex()] then + EnsureLoot() + + ply.ActiveLootCache = ent + ply.LootOpenTime = CurTime() + + if ply.TarkovData then + ply.TarkovData.Containers.cache = table.Copy(ent.CacheInventory) + net.Start(TAG .. "_Update") + net.WriteTable(ply.TarkovData) + net.WriteBool(true) + net.Send(ply) + ply:EmitSound("items/ammo_pickup.wav") + ply:ConCommand("tarkov_open_inventory") + end + return false + end - ply.ActiveLootCache = ent - ply.LootOpenTime = CurTime() + -- 1. START SEARCHING (Visuals) + ply.IsSearching = true + ply.SearchEndTime = CurTime() + 3.5 -- Safety timeout - if ply.TarkovData then - ply.TarkovData.Containers.cache = table.Copy(ent.CacheInventory) - net.Start(TAG .. "_Update") - net.WriteTable(ply.TarkovData) - net.WriteBool(true) - net.Send(ply) - ply:EmitSound("items/ammo_pickup.wav") - ply:ConCommand("tarkov_open_inventory") - end - return false - end + ply:EmitSound("physics/cardboard/cardboard_box_impact_soft2.wav") - -- 1. START SEARCHING (Visuals) - ply.IsSearching = true - ply.SearchEndTime = CurTime() + 3.5 -- Safety timeout + -- Send Search Progress Bar to Client + net.Start(TAG .. "_SearchUI") + net.WriteFloat(3.0) -- 3.0 Seconds duration + net.Send(ply) - ply:EmitSound("physics/cardboard/cardboard_box_impact_soft2.wav") + -- 2. TIMER (Logic) + timer.Create("TarkovSearch_" .. ply:SteamID64(), 3.0, 1, function() + if not IsValid(ply) then return end + ply.IsSearching = false - -- Send Search Progress Bar to Client - net.Start(TAG .. "_SearchUI") - net.WriteFloat(3.0) -- 3.0 Seconds duration - net.Send(ply) + if not IsValid(ent) then return end - -- 2. TIMER (Logic) - timer.Create("TarkovSearch_" .. ply:SteamID64(), 3.0, 1, function() - if not IsValid(ply) then return end - ply.IsSearching = false + -- Validate Distance + if ply:GetPos():DistToSqr(ent:GetPos()) > 150*150 then + ply:ChatPrint("You moved too far away.") + return + end - if not IsValid(ent) then return end + -- 3. GENERATE LOOT + EnsureLoot() - -- Validate Distance - if ply:GetPos():DistToSqr(ent:GetPos()) > 150*150 then - ply:ChatPrint("You moved too far away.") - return - end + -- 4. OPEN INVENTORY MENU + ply.ActiveLootCache = ent + ply.LootOpenTime = CurTime() - -- 3. GENERATE LOOT - EnsureLoot() + -- Mark as searched + if not ply.SearchedCaches then ply.SearchedCaches = {} end + ply.SearchedCaches[ent:EntIndex()] = true - -- 4. OPEN INVENTORY MENU - -- Set this entity as the player's active cache session - ply.ActiveLootCache = ent - ply.LootOpenTime = CurTime() + -- Sync the cache data to the player's "cache" container slot in their session + if ply.TarkovData then + ply.TarkovData.Containers.cache = table.Copy(ent.CacheInventory) - -- Mark as searched - if not ply.SearchedCaches then ply.SearchedCaches = {} end - ply.SearchedCaches[ent:EntIndex()] = true + -- Send update to client (This opens the menu because IsCacheOpen will be true) + net.Start(TAG .. "_Update") + net.WriteTable(ply.TarkovData) + net.WriteBool(true) -- Tell client cache is OPEN + net.Send(ply) - -- Sync the cache data to the player's "cache" container slot in their session - if ply.TarkovData then - ply.TarkovData.Containers.cache = table.Copy(ent.CacheInventory) + ply:EmitSound("items/ammo_pickup.wav") + + -- Force open menu command just in case + ply:ConCommand("tarkov_open_inventory") + end + end) + + -- Return false to BLOCK the entity's default behavior + return false +end) + +-- FIX: Distance Check Loop +-- Ensure inventory closes if player walks away while it's open +hook.Add("Think", "TarkovLootDistanceCheck", function() + for _, ply in ipairs(player.GetAll()) do + if IsValid(ply.ActiveLootCache) then + -- Check distance + if ply:GetPos():DistToSqr(ply.ActiveLootCache:GetPos()) > 150*150 then + -- Close it + ply.ActiveLootCache = nil - -- Send update to client (This opens the menu because IsCacheOpen will be true) + -- Send Close Update net.Start(TAG .. "_Update") - net.WriteTable(ply.TarkovData) - net.WriteBool(true) -- Tell client cache is OPEN + if ply.TarkovData then + net.WriteTable(ply.TarkovData) + else + net.WriteTable({}) + end + net.WriteBool(false) net.Send(ply) - ply:EmitSound("items/ammo_pickup.wav") - - -- Force open menu command just in case - ply:ConCommand("tarkov_open_inventory") + ply:ChatPrint("You moved too far away.") end - end) - - -- Return false to BLOCK the entity's default behavior - -- (e.g. stop the workshop addon from opening its own menu) - return false - end) \ No newline at end of file + end + end +end) diff --git a/Escape from GMOD utils/lua/autorun/sh_tarkov_inventory.lua b/Escape from GMOD utils/lua/autorun/sh_tarkov_inventory.lua index a5a899f..5856737 100644 --- a/Escape from GMOD utils/lua/autorun/sh_tarkov_inventory.lua +++ b/Escape from GMOD utils/lua/autorun/sh_tarkov_inventory.lua @@ -236,6 +236,56 @@ if SERVER then end end end + + elseif action == "quick_move" then + local container = net.ReadString() + local index = net.ReadUInt(8) + + -- If coming from a container (cache, pockets, backpack, rig) + local list = ply.TarkovData.Containers[container] + if list and list[index] then + local itemID = list[index] + local itemData = ITEMS[itemID] + + -- Try to Auto-Equip first + if itemData.Type == "equip" and not ply.TarkovData.Equipment[itemData.Slot] then + ply.TarkovData.Containers[container][index] = nil + ply.TarkovData.Equipment[itemData.Slot] = itemID + + if string.sub(itemID, 1, 6) == "weapon" then ply:Give(itemID) + elseif itemID == "armor_hev" then ply:EquipSuit(); ply:SetArmor(100) end + if itemData.Slot == "Backpack" then ply:SetNWString("TarkovBackpack", itemData.Model) end + + SyncInventory(ply) + return + end + + -- Else move to best available container (that isn't self if possible, but simplicity first) + -- If in Cache, try Pockets/Rig/Backpack + if container == "cache" then + if AddItemToInventory(ply, itemID) then + ply.TarkovData.Containers[container][index] = nil + SyncInventory(ply) + end + else + -- If in inventory, try to move to Cache if open? + -- Or just move to another container? + -- For now, "Quick Move" usually implies "Loot" -> "Inventory" or "Equip" + -- Let's support Inventory -> Cache if Cache is open + if IsValid(ply.ActiveLootCache) then + -- Try to add to cache + local cacheCap = 20 + for i=1, cacheCap do + if not ply.TarkovData.Containers.cache[i] then + ply.TarkovData.Containers.cache[i] = itemID + ply.TarkovData.Containers[container][index] = nil + SyncInventory(ply) + return + end + end + end + end + end end ply:ChatPrint("[Inventory] No space in Pockets/Rig/Backpack!") @@ -521,6 +571,17 @@ if CLIENT then onClick() return end + -- Ctrl+Click Logic + if code == MOUSE_LEFT and input.IsKeyDown(KEY_LCONTROL) then + if draggableData then -- draggableData contains { Container = "...", Index = ... } + net.Start(TAG .. "_Action") + net.WriteString("quick_move") + net.WriteString(draggableData.Container) + net.WriteUInt(draggableData.Index, 8) + net.SendToServer() + return + end + end if baseMousePressed then baseMousePressed(s, code) end end