local PLUGIN = PLUGIN ix.util.Include("sh_config.lua") -- Цветовая схема (как в арсенале) local COLOR_BG_DARK = Color(3, 5, 4) local COLOR_BG_MEDIUM = Color(8, 12, 10) local COLOR_BG_LIGHT = Color(12, 18, 14) local COLOR_PRIMARY = Color(27, 94, 32) local COLOR_PRIMARY_DARK = Color(15, 60, 18) local COLOR_ACCENT = Color(56, 102, 35) local COLOR_TEXT_PRIMARY = Color(165, 214, 167) local COLOR_TEXT_SECONDARY = Color(174, 213, 129) local COLOR_DANGER = Color(229, 57, 53) local COLOR_WARNING = Color(255, 193, 7) local COLOR_BORDER = Color(46, 125, 50, 100) surface.CreateFont("ixShopPriceFont", { font = "Roboto", size = 28, weight = 900, antialias = true }) surface.CreateFont("ixShopNameFont", { font = "Roboto", size = 20, weight = 700, antialias = true }) -- Вспомогательная функция для рисования кругов if not draw.Circle then function draw.Circle(x, y, radius, color) local segmentCount = math.max(16, radius) surface.SetDrawColor(color or color_white) local circle = {} for i = 0, segmentCount do local angle = math.rad((i / segmentCount) * 360) table.insert(circle, { x = x + math.cos(angle) * radius, y = y + math.sin(angle) * radius }) end surface.DrawPoly(circle) end end if not surface.DrawCircle then function surface.DrawCircle(x, y, radius, color) draw.Circle(x, y, radius, color) end end -- Получение меню магазина net.Receive("ixShop_OpenMenu", function() local shopType = net.ReadString() local shopData = net.ReadTable() PLUGIN:OpenShopMenu(shopType, shopData) end) -- Создание меню магазина function PLUGIN:OpenShopMenu(shopType, shopData) if IsValid(self.shopFrame) then self.shopFrame:Remove() end local scrW, scrH = ScrW(), ScrH() local frame = vgui.Create("DFrame") frame:SetSize(math.min(1200, scrW - 100), math.min(800, scrH - 100)) frame:Center() frame:SetTitle("") frame:SetDraggable(true) frame:ShowCloseButton(false) frame:MakePopup() frame:SetDeleteOnClose(true) self.shopFrame = frame -- Градиентный фон с зеленым оттенком (как в арсенале) frame.Paint = function(s, w, h) -- Основной фон surface.SetDrawColor(COLOR_BG_DARK) surface.DrawRect(0, 0, w, h) -- Градиент сверху local gradHeight = 200 for i = 0, gradHeight do local alpha = (1 - i/gradHeight) * 40 surface.SetDrawColor(COLOR_PRIMARY.r, COLOR_PRIMARY.g, COLOR_PRIMARY.b, alpha) surface.DrawRect(0, i, w, 1) end -- Верхняя панель surface.SetDrawColor(COLOR_BG_MEDIUM) surface.DrawRect(0, 0, w, 80) -- Акцентная линия surface.SetDrawColor(COLOR_PRIMARY) surface.DrawRect(0, 80, w, 3) -- Декоративные элементы surface.SetDrawColor(COLOR_BORDER) for i = 0, w, 150 do surface.DrawRect(i, 0, 1, 80) end -- Заголовок draw.SimpleText("◆ " .. (shopData.name or "МАГАЗИН") .. " ◆", "ixMenuButtonFont", w/2, 28, COLOR_ACCENT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) draw.SimpleText((shopData.description or ""), "ixSmallFont", w/2, 55, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end -- Кнопка закрытия local closeBtn = vgui.Create("DButton", frame) closeBtn:SetText("") closeBtn:SetSize(38, 38) closeBtn:SetPos(frame:GetWide() - 50, 12) closeBtn.Paint = function(s, w, h) local col = s:IsHovered() and COLOR_DANGER or Color(COLOR_DANGER.r * 0.7, COLOR_DANGER.g * 0.7, COLOR_DANGER.b * 0.7) -- Фон кнопки draw.RoundedBox(4, 0, 0, w, h, col) -- Внешняя рамка surface.SetDrawColor(COLOR_BG_DARK) surface.DrawOutlinedRect(0, 0, w, h, 2) -- Крестик surface.SetDrawColor(COLOR_TEXT_PRIMARY) surface.DrawLine(w*0.3, h*0.3, w*0.7, h*0.7) surface.DrawLine(w*0.7, h*0.3, w*0.3, h*0.7) if s:IsHovered() then surface.SetDrawColor(255, 255, 255, 30) draw.RoundedBox(4, 0, 0, w, h, Color(255, 255, 255, 30)) end end closeBtn.DoClick = function() frame:Close() end -- Боковая панель категорий local sidePanel = vgui.Create("DPanel", frame) sidePanel:SetSize(250, frame:GetTall() - 110) sidePanel:SetPos(20, 90) sidePanel.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_MEDIUM) surface.SetDrawColor(COLOR_BORDER) surface.DrawOutlinedRect(0, 0, w, h, 2) end local categoryList = vgui.Create("DScrollPanel", sidePanel) categoryList:Dock(FILL) categoryList:DockMargin(5, 5, 5, 5) -- Панель товаров (основная) local contentPanel = vgui.Create("DPanel", frame) contentPanel:SetSize(frame:GetWide() - 300, frame:GetTall() - 110) contentPanel:SetPos(280, 90) contentPanel.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_MEDIUM) surface.SetDrawColor(COLOR_BORDER) surface.DrawOutlinedRect(0, 0, w, h, 2) end local itemList = vgui.Create("DScrollPanel", contentPanel) itemList:SetSize(contentPanel:GetWide() - 20, contentPanel:GetTall() - 20) itemList:SetPos(10, 10) local function LoadCategory(catID) itemList:Clear() local data = PLUGIN.shops[catID] if not data then return end local items = data.items or {} if table.Count(items) == 0 then local noItems = vgui.Create("DLabel", itemList) noItems:SetText("В этой категории пока нет товаров.") noItems:SetFont("ixMediumFont") noItems:SetTextColor(Color(200, 50, 50)) noItems:SizeToContents() noItems:SetPos(itemList:GetWide()/2 - noItems:GetWide()/2, 100) return end for itemID, itemData in SortedPairs(items) do local ixItem = ix.item.list[itemID] if not ixItem then continue end local itemPanel = vgui.Create("DPanel", itemList) itemPanel:SetSize(itemList:GetWide() - 20, 100) itemPanel:Dock(TOP) itemPanel:DockMargin(0, 0, 10, 8) local isHovered = false itemPanel.Paint = function(s, w, h) local bgColor = COLOR_BG_LIGHT if isHovered then bgColor = Color(bgColor.r + 10, bgColor.g + 15, bgColor.b + 10) end draw.RoundedBox(6, 0, 0, w, h, bgColor) draw.RoundedBoxEx(6, 0, 0, 5, h, COLOR_PRIMARY, true, false, true, false) surface.SetDrawColor(COLOR_BORDER) surface.DrawOutlinedRect(0, 0, w, h, 2) end itemPanel.OnCursorEntered = function() isHovered = true end itemPanel.OnCursorExited = function() isHovered = false end -- Иконка local itemIcon = vgui.Create("DModelPanel", itemPanel) itemIcon:SetSize(80, 80) itemIcon:SetPos(15, 10) itemIcon:SetFOV(30) function itemIcon:LayoutEntity(ent) return end if ixItem.model then itemIcon:SetModel(ixItem.model) itemIcon.LayoutEntity = function() end local ent = itemIcon:GetEntity() if IsValid(ent) then local mins, maxs = ent:GetRenderBounds() local size = math.max(maxs.x - mins.x, maxs.y - mins.y, maxs.z - mins.z) local center = (mins + maxs) / 2 if shopType == "consumables" then itemIcon:SetFOV(18) itemIcon:SetCamPos(center + Vector(size * 0.6, size * 0.6, size * 0.4)) itemIcon:SetLookAt(center) elseif shopType == "ammo_basic" or shopType == "ammo_special" then itemIcon:SetFOV(45) itemIcon:SetCamPos(center + Vector(size * 2.2, size * 2.2, size * 1.2)) itemIcon:SetLookAt(center) else itemIcon:SetFOV(30) itemIcon:SetCamPos(center + Vector(size * 1.4, size * 1.4, size * 0.8)) itemIcon:SetLookAt(center) end end end -- Название local nameLabel = vgui.Create("DLabel", itemPanel) nameLabel:SetText(ixItem.name or itemID) nameLabel:SetFont("ixShopNameFont") nameLabel:SetTextColor(COLOR_TEXT_PRIMARY) nameLabel:SetPos(110, 25) nameLabel:SizeToContents() -- Цена local price = itemData.price or 0 local priceLabel = vgui.Create("DLabel", itemPanel) priceLabel:SetText(price .. " ₽") priceLabel:SetFont("ixShopPriceFont") priceLabel:SetTextColor(COLOR_ACCENT) priceLabel:SetPos(110, 55) priceLabel:SizeToContents() -- Кнопка покупки local buyButton = vgui.Create("DButton", itemPanel) buyButton:SetSize(120, 40) buyButton:SetPos(itemPanel:GetWide() - 135, 30) buyButton:SetText("") buyButton.Paint = function(s, w, h) local col = s:IsHovered() and COLOR_ACCENT or COLOR_PRIMARY draw.RoundedBox(6, 0, 0, w, h, col) draw.SimpleText("КУПИТЬ ►", "ixSmallFont", w/2, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end buyButton.DoClick = function() net.Start("ixShop_BuyItem") net.WriteString(catID) net.WriteString(itemID) net.SendToServer() end end end -- Заполнение списка категорий for id, data in SortedPairsByMemberValue(PLUGIN.shops, "name") do local catBtn = categoryList:Add("DButton") catBtn:SetText(data.name or id) catBtn:SetFont("ixSmallFont") catBtn:Dock(TOP) catBtn:SetTall(45) catBtn:DockMargin(0, 0, 0, 5) catBtn:SetTextColor(COLOR_TEXT_PRIMARY) catBtn.Paint = function(s, w, h) local active = (shopType == id) local col = active and COLOR_PRIMARY or COLOR_BG_LIGHT if s:IsHovered() then col = COLOR_ACCENT end draw.RoundedBox(4, 0, 0, w, h, col) if active then draw.RoundedBox(0, 0, 0, 4, h, COLOR_TEXT_PRIMARY) end end catBtn.DoClick = function() shopType = id LoadCategory(id) end end -- Загружаем начальную категорию LoadCategory(shopType) end -- Добавление опций в C-Menu (свойства сущности) properties.Add("ixShopModel", { MenuLabel = "Изменить модель NPC", Order = 999, MenuIcon = "icon16/user_edit.png", Filter = function(self, ent, ply) if (ent:GetClass() != "ix_shop_npc") then return false end if (!ply:IsAdmin()) then return false end return true end, Action = function(self, ent) Derma_StringRequest( "Изменить модель NPC", "Введите путь к модели:", ent:GetModel(), function(text) net.Start("ixShop_UpdateModel") net.WriteEntity(ent) net.WriteString(text) net.SendToServer() end ) end }) properties.Add("ixShopType", { MenuLabel = "Изменить тип магазина", Order = 1000, MenuIcon = "icon16/cart_edit.png", Filter = function(self, ent, ply) if (ent:GetClass() != "ix_shop_npc") then return false end if (!ply:IsAdmin()) then return false end return true end, MenuOpen = function(self, option, ent, ply) local plugin = ix.plugin.Get("shop") if (not plugin) then return end local subMenu = option:AddSubMenu() for typeID, data in pairs(plugin.shops) do local target = subMenu:AddOption(data.name or typeID, function() net.Start("ixShop_UpdateType") net.WriteEntity(ent) net.WriteString(typeID) net.SendToServer() end) target:SetIcon("icon16/tag_blue.png") end end, Action = function(self, ent) -- Опции обрабатываются в MenuOpen end })