local PLUGIN = PLUGIN util.AddNetworkString("ixStorageOpen") util.AddNetworkString("ixStorageDeposit") util.AddNetworkString("ixStorageWithdraw") util.AddNetworkString("ixStorageSync") -- Robust and STABLE data retrieval: ensures we ALWAYS return a sequential numeric array -- handled in a stable order based on original keys. function PLUGIN:GetStorage(char) local data = char:GetData("personal_storage", {}) if (!istable(data)) then return {} end local fixedData = {} local keys = {} for k, _ in pairs(data) do local n = tonumber(k) if (n) then table.insert(keys, n) end end table.sort(keys) -- Ensure key stability (1 before 2, etc) for _, k in ipairs(keys) do local v = data[k] or data[tostring(k)] if (istable(v) and v.type) then table.insert(fixedData, v) end end return fixedData end function PLUGIN:SaveStorage(char, data) -- We save it as a clean array. Helix will JSON-encode it. char:SetData("personal_storage", data) end net.Receive("ixStorageDeposit", function(len, ply) local char = ply:GetCharacter() if (!char) then return end local type = net.ReadString() local id = net.ReadString() local storage = PLUGIN:GetStorage(char) local entry = {type = type} if (type == "weapon") then local weapon = ply:GetWeapon(id) if (!IsValid(weapon)) then net.Start("ixStorageSync") net.WriteTable(storage) net.Send(ply) return end local blacklist = {["weapon_physgun"] = true, ["gmod_tool"] = true, ["gmod_camera"] = true} if (blacklist[id]) then net.Start("ixStorageSync") net.WriteTable(storage) net.Send(ply) return end entry.class = id entry.name = weapon:GetPrintName() or id entry.clip1 = weapon:Clip1() entry.clip2 = weapon:Clip2() ply:StripWeapon(id) table.insert(storage, entry) PLUGIN:SaveStorage(char, storage) net.Start("ixStorageSync") net.WriteTable(storage) net.Send(ply) elseif (type == "item") then local inv = char:GetInventory() local itemID = tonumber(id) local item = inv:GetItemByID(itemID) if (!item) then net.Start("ixStorageSync") net.WriteTable(storage) net.Send(ply) return end entry.uniqueID = item.uniqueID entry.data = item.data or {} entry.name = item:GetName() or item.name item:Remove():next(function() -- Add to storage only after successful removal from inventory local currentStorage = PLUGIN:GetStorage(char) table.insert(currentStorage, entry) PLUGIN:SaveStorage(char, currentStorage) net.Start("ixStorageSync") net.WriteTable(currentStorage) net.Send(ply) end):catch(function(err) net.Start("ixStorageSync") net.WriteTable(PLUGIN:GetStorage(char)) net.Send(ply) ply:Notify("Ошибка удаления: " .. (err or "неизвестно")) end) end end) net.Receive("ixStorageWithdraw", function(len, ply) local char = ply:GetCharacter() if (!char) then return end local index = net.ReadUInt(8) local storage = PLUGIN:GetStorage(char) local entry = storage[index] if (!entry) then net.Start("ixStorageSync") net.WriteTable(storage) net.Send(ply) return end -- PRE-EMPTIVE REMOVAL to prevent duplication exploit/bug table.remove(storage, index) PLUGIN:SaveStorage(char, storage) if (entry.type == "weapon") then local weapon = ply:Give(entry.class) if (IsValid(weapon)) then weapon:SetClip1(entry.clip1 or 0) weapon:SetClip2(entry.clip2 or 0) -- Already removed from storage, just sync net.Start("ixStorageSync") net.WriteTable(storage) net.Send(ply) else -- FAIL: Put back local currentStorage = PLUGIN:GetStorage(char) table.insert(currentStorage, entry) PLUGIN:SaveStorage(char, currentStorage) net.Start("ixStorageSync") net.WriteTable(currentStorage) net.Send(ply) end else local inv = char:GetInventory() inv:Add(entry.uniqueID, 1, entry.data):next(function(res) -- Already removed from storage, just sync to confirm removal net.Start("ixStorageSync") net.WriteTable(PLUGIN:GetStorage(char)) net.Send(ply) end):catch(function(err) -- FAIL: Put back local currentStorage = PLUGIN:GetStorage(char) table.insert(currentStorage, entry) PLUGIN:SaveStorage(char, currentStorage) net.Start("ixStorageSync") net.WriteTable(currentStorage) net.Send(ply) ply:Notify("Нет места в инвентаре! Предмет возвращен в шкаф.") end) end end) function PLUGIN:OpenStorage(ply) local char = ply:GetCharacter() if (!char) then return end local data = self:GetStorage(char) net.Start("ixStorageSync") net.WriteTable(data) net.Send(ply) end