add sborka
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
local storageData = {}
|
||||
|
||||
net.Receive("ixStorageSync", function()
|
||||
storageData = net.ReadTable()
|
||||
|
||||
if (IsValid(ix.gui.personalStorage)) then
|
||||
ix.gui.personalStorage:UpdateStorage(storageData)
|
||||
ix.gui.personalStorage:UpdatePlayerItems()
|
||||
else
|
||||
PLUGIN:OpenUI()
|
||||
end
|
||||
end)
|
||||
|
||||
function PLUGIN:OpenUI()
|
||||
if (IsValid(ix.gui.personalStorage)) then
|
||||
ix.gui.personalStorage:Remove()
|
||||
end
|
||||
|
||||
local frame = vgui.Create("DFrame")
|
||||
frame:SetSize(800, 600)
|
||||
frame:Center()
|
||||
frame:SetTitle("ПЕРСОНАЛЬНЫЙ СКЛАД")
|
||||
frame:MakePopup()
|
||||
ix.gui.personalStorage = frame
|
||||
|
||||
frame.Paint = function(self, w, h)
|
||||
surface.SetDrawColor(30, 30, 30, 240)
|
||||
surface.DrawRect(0, 0, w, h)
|
||||
local accent = ix.config.Get("color") or Color(100, 150, 100)
|
||||
surface.SetDrawColor(accent)
|
||||
surface.DrawOutlinedRect(0, 0, w, h)
|
||||
|
||||
surface.SetDrawColor(accent.r, accent.g, accent.b, 50)
|
||||
surface.DrawRect(0, 0, w, 24)
|
||||
end
|
||||
|
||||
local leftPanel = frame:Add("DPanel")
|
||||
leftPanel:Dock(LEFT)
|
||||
leftPanel:SetWide(385)
|
||||
leftPanel:DockMargin(10, 30, 5, 10)
|
||||
leftPanel.Paint = function(self, w, h)
|
||||
surface.SetDrawColor(0, 0, 0, 100)
|
||||
surface.DrawRect(0, 0, w, h)
|
||||
end
|
||||
|
||||
local rightPanel = frame:Add("DPanel")
|
||||
rightPanel:Dock(RIGHT)
|
||||
rightPanel:SetWide(385)
|
||||
rightPanel:DockMargin(5, 30, 10, 10)
|
||||
rightPanel.Paint = function(self, w, h)
|
||||
surface.SetDrawColor(0, 0, 0, 100)
|
||||
surface.DrawRect(0, 0, w, h)
|
||||
end
|
||||
|
||||
local pScroll = leftPanel:Add("DScrollPanel")
|
||||
pScroll:Dock(FILL)
|
||||
pScroll:DockMargin(5, 5, 5, 5)
|
||||
|
||||
function frame:UpdatePlayerItems()
|
||||
pScroll:Clear()
|
||||
|
||||
local wepHeader = pScroll:Add("DLabel")
|
||||
wepHeader:SetText("ОРУЖИЕ В РУКАХ")
|
||||
wepHeader:SetFont("ixSmallFont")
|
||||
wepHeader:Dock(TOP)
|
||||
wepHeader:SetContentAlignment(5)
|
||||
wepHeader:SetTall(25)
|
||||
wepHeader:SetTextColor(ix.config.Get("color"))
|
||||
|
||||
for _, wep in pairs(LocalPlayer():GetWeapons()) do
|
||||
if (!IsValid(wep)) then continue end
|
||||
|
||||
local wepName = wep:GetPrintName()
|
||||
local class = wep:GetClass()
|
||||
|
||||
local btn = pScroll:Add("ixMenuButton")
|
||||
btn:Dock(TOP)
|
||||
btn:SetTall(35)
|
||||
btn:SetText(wepName)
|
||||
btn.DoClick = function()
|
||||
if (frame.nextClick and frame.nextClick > CurTime()) then return end
|
||||
frame.nextClick = CurTime() + 1.0 -- Increase cooldown for safety
|
||||
|
||||
net.Start("ixStorageDeposit")
|
||||
net.WriteString("weapon")
|
||||
net.WriteString(class)
|
||||
net.SendToServer()
|
||||
|
||||
btn:SetEnabled(false)
|
||||
btn:SetText("ОЖИДАНИЕ...")
|
||||
|
||||
-- Fail-safe to re-enable UI if server doesn't respond
|
||||
timer.Simple(2, function()
|
||||
if (IsValid(btn) and !btn:IsEnabled()) then
|
||||
btn:SetEnabled(true)
|
||||
btn:SetText(wepName)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
local invHeader = pScroll:Add("DLabel")
|
||||
invHeader:SetText("ПРЕДМЕТЫ В ИНВЕНТАРЕ")
|
||||
invHeader:SetFont("ixSmallFont")
|
||||
invHeader:Dock(TOP)
|
||||
invHeader:SetContentAlignment(5)
|
||||
invHeader:SetTall(25)
|
||||
invHeader:DockMargin(0, 10, 0, 0)
|
||||
invHeader:SetTextColor(ix.config.Get("color"))
|
||||
|
||||
local char = LocalPlayer():GetCharacter()
|
||||
if (char) then
|
||||
local inv = char:GetInventory()
|
||||
if (inv) then
|
||||
for _, item in pairs(inv:GetItems()) do
|
||||
local btn = pScroll:Add("ixMenuButton")
|
||||
btn:Dock(TOP)
|
||||
btn:SetTall(35)
|
||||
btn:SetText(item:GetName())
|
||||
local originalText = item:GetName()
|
||||
btn.DoClick = function()
|
||||
if (frame.nextClick and frame.nextClick > CurTime()) then return end
|
||||
frame.nextClick = CurTime() + 1.0
|
||||
|
||||
net.Start("ixStorageDeposit")
|
||||
net.WriteString("item")
|
||||
net.WriteString(tostring(item:GetID()))
|
||||
net.SendToServer()
|
||||
|
||||
btn:SetEnabled(false)
|
||||
btn:SetText("ОЖИДАНИЕ...")
|
||||
|
||||
timer.Simple(2, function()
|
||||
if (IsValid(btn) and !btn:IsEnabled()) then
|
||||
btn:SetEnabled(true)
|
||||
btn:SetText(originalText)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
frame:UpdatePlayerItems()
|
||||
|
||||
local sScroll = rightPanel:Add("DScrollPanel")
|
||||
sScroll:Dock(FILL)
|
||||
sScroll:DockMargin(5, 5, 5, 5)
|
||||
|
||||
local storageHeader = sScroll:Add("DLabel")
|
||||
storageHeader:SetText("СОДЕРЖИМОЕ ХРАНИЛИЩА")
|
||||
storageHeader:SetFont("ixSmallFont")
|
||||
storageHeader:Dock(TOP)
|
||||
storageHeader:SetContentAlignment(5)
|
||||
storageHeader:SetTall(25)
|
||||
storageHeader:SetTextColor(ix.config.Get("color"))
|
||||
|
||||
local grid = sScroll:Add("DIconLayout")
|
||||
grid:Dock(TOP)
|
||||
grid:SetSpaceX(5)
|
||||
grid:SetSpaceY(5)
|
||||
|
||||
function frame:UpdateStorage(data)
|
||||
grid:Clear()
|
||||
|
||||
for i, entry in ipairs(data) do
|
||||
if (!istable(entry)) then continue end
|
||||
|
||||
local icon = grid:Add("SpawnIcon")
|
||||
|
||||
local model = "models/error.mdl"
|
||||
if (entry.type == "weapon" and entry.class) then
|
||||
local wepTable = weapons.Get(entry.class)
|
||||
model = (wepTable and wepTable.WorldModel) or "models/weapons/w_pistol.mdl"
|
||||
elseif (entry.type == "item" and entry.uniqueID) then
|
||||
local itemTable = ix.item.list[entry.uniqueID]
|
||||
model = (itemTable and itemTable.model) or "models/props_junk/garbage_bag001a.mdl"
|
||||
end
|
||||
|
||||
icon:SetModel(model)
|
||||
icon:SetSize(64, 64)
|
||||
icon:SetTooltip(entry.name or entry.class or entry.uniqueID or "Неизвестный предмет")
|
||||
icon.DoClick = function()
|
||||
if (frame.nextClick and frame.nextClick > CurTime()) then return end
|
||||
frame.nextClick = CurTime() + 1.0
|
||||
|
||||
net.Start("ixStorageWithdraw")
|
||||
net.WriteUInt(i, 8)
|
||||
net.SendToServer()
|
||||
|
||||
icon:SetAlpha(100)
|
||||
|
||||
timer.Simple(2, function()
|
||||
if (IsValid(icon)) then icon:SetAlpha(255) end
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
frame:UpdateStorage(storageData)
|
||||
end
|
||||
@@ -0,0 +1,40 @@
|
||||
AddCSLuaFile()
|
||||
|
||||
ENT.Type = "anim"
|
||||
ENT.Base = "base_gmodentity"
|
||||
ENT.PrintName = "Личный склад"
|
||||
ENT.Author = "Scripty"
|
||||
ENT.Category = "Helix"
|
||||
ENT.Spawnable = true
|
||||
ENT.AdminOnly = true
|
||||
|
||||
if SERVER then
|
||||
function ENT:Initialize()
|
||||
self:SetModel("models/props_c17/Lockers001a.mdl")
|
||||
self:PhysicsInit(SOLID_VPHYSICS)
|
||||
self:SetMoveType(MOVETYPE_VPHYSICS)
|
||||
self:SetSolid(SOLID_VPHYSICS)
|
||||
self:SetUseType(SIMPLE_USE)
|
||||
|
||||
local phys = self:GetPhysicsObject()
|
||||
if IsValid(phys) then
|
||||
phys:Wake()
|
||||
phys:EnableMotion(false)
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:Use(activator)
|
||||
if (IsValid(activator) and activator:IsPlayer() and activator:GetCharacter()) then
|
||||
local plugin = ix.plugin.list["personal_storage"]
|
||||
if (plugin) then
|
||||
plugin:OpenStorage(activator)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
function ENT:Draw()
|
||||
self:DrawModel()
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1 @@
|
||||
-- Empty config file for future use
|
||||
@@ -0,0 +1,8 @@
|
||||
local PLUGIN = PLUGIN or {}
|
||||
PLUGIN.name = "Personal Storage"
|
||||
PLUGIN.author = "Scripty"
|
||||
PLUGIN.description = "Character-based weapon storage system."
|
||||
|
||||
ix.util.Include("sh_config.lua")
|
||||
ix.util.Include("sv_plugin.lua")
|
||||
ix.util.Include("cl_plugin.lua")
|
||||
@@ -0,0 +1,180 @@
|
||||
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
|
||||
Reference in New Issue
Block a user