Files
VnUtest/garrysmod/gamemodes/militaryrp/plugins/shop/cl_plugin.lua
2026-03-31 10:27:04 +03:00

372 lines
11 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
})