-- Клиентские данные local PLUGIN = PLUGIN PLUGIN.promoCodesList = PLUGIN.promoCodesList or {} -- Получение результата активации net.Receive("ixPromoCodeResult", function() local success = net.ReadBool() local message = net.ReadString() if success then notification.AddLegacy(message, NOTIFY_GENERIC, 5) surface.PlaySound("buttons/button15.wav") else notification.AddLegacy(message, NOTIFY_ERROR, 5) surface.PlaySound("buttons/button10.wav") end LocalPlayer():Notify(message) end) -- Получение списка промокодов (для админов) net.Receive("ixPromoCodeSync", function() local jsonData = net.ReadString() PLUGIN.promoCodesList = util.JSONToTable(jsonData) or {} end) -- Функция активации промокода function PLUGIN:ActivatePromoCode(code) net.Start("ixPromoCodeActivate") net.WriteString(code) net.SendToServer() end -- Админ-панель управления промокодами function PLUGIN:OpenPromoCodesAdmin() if not LocalPlayer():IsSuperAdmin() then LocalPlayer():Notify("У вас нет доступа к этой панели") return end -- Запрашиваем список промокодов net.Start("ixPromoCodeList") net.SendToServer() local frame = vgui.Create("DFrame") frame:SetSize(1200, 700) frame:Center() frame:SetTitle("") frame:SetDraggable(true) frame:ShowCloseButton(false) frame:MakePopup() frame.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, Color(18, 18, 20)) surface.SetDrawColor(1, 67, 29) surface.DrawRect(0, 0, w, 3) draw.SimpleText("УПРАВЛЕНИЕ ПРОМОКОДАМИ", "F4Menu_DonateHero", w/2, 30, Color(1, 67, 29), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end local closeBtn = vgui.Create("DButton", frame) closeBtn:SetPos(frame:GetWide() - 35, 5) closeBtn:SetSize(30, 30) closeBtn:SetText("✕") closeBtn:SetFont("F4Menu_Category") closeBtn:SetTextColor(Color(255, 255, 255)) closeBtn.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, s:IsHovered() and Color(200, 50, 50) or Color(0, 0, 0, 0)) end closeBtn.DoClick = function() frame:Close() end -- Панель создания промокода local createPanel = vgui.Create("DPanel", frame) createPanel:SetPos(20, 70) createPanel:SetSize(550, 600) createPanel.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, Color(25, 25, 28)) draw.SimpleText("СОЗДАТЬ ПРОМОКОД", "F4Menu_Category", w/2, 20, Color(200, 200, 200), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end local yPos = 60 -- Переменные для доступа в обработчиках local codeEntry local isCustomCode = false -- Тип промокода local typeLabel = vgui.Create("DLabel", createPanel) typeLabel:SetPos(20, yPos) typeLabel:SetSize(200, 25) typeLabel:SetFont("F4Menu_Item") typeLabel:SetTextColor(Color(200, 200, 200)) typeLabel:SetText("Тип промокода:") local typeSelector = vgui.Create("DPanel", createPanel) typeSelector:SetPos(20, yPos + 30) typeSelector:SetSize(510, 40) typeSelector.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, Color(35, 35, 38)) end local randomBtn = vgui.Create("DButton", typeSelector) randomBtn:SetPos(5, 5) randomBtn:SetSize(250, 30) randomBtn:SetText("") randomBtn.Paint = function(s, w, h) local active = not isCustomCode draw.RoundedBox(6, 0, 0, w, h, active and Color(1, 67, 29) or Color(45, 45, 48)) draw.SimpleText("Случайный код", "F4Menu_Item", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end randomBtn.DoClick = function() isCustomCode = false codeEntry:SetEnabled(false) codeEntry:SetValue("") codeEntry:SetPlaceholderText("Будет сгенерирован автоматически") end local customBtn = vgui.Create("DButton", typeSelector) customBtn:SetPos(255, 5) customBtn:SetSize(250, 30) customBtn:SetText("") customBtn.Paint = function(s, w, h) local active = isCustomCode draw.RoundedBox(6, 0, 0, w, h, active and Color(1, 67, 29) or Color(45, 45, 48)) draw.SimpleText("Именной код", "F4Menu_Item", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end customBtn.DoClick = function() isCustomCode = true codeEntry:SetEnabled(true) codeEntry:SetValue("") codeEntry:SetPlaceholderText("NEW_YEAR2026") end yPos = yPos + 85 -- Код промокода local codeLabel = vgui.Create("DLabel", createPanel) codeLabel:SetPos(20, yPos) codeLabel:SetSize(400, 25) codeLabel:SetFont("F4Menu_Item") codeLabel:SetTextColor(Color(200, 200, 200)) codeLabel:SetText("Код промокода:") local examplesLabel = vgui.Create("DLabel", createPanel) examplesLabel:SetPos(20, yPos + 20) examplesLabel:SetSize(510, 15) examplesLabel:SetFont("F4Menu_InfoSmall") examplesLabel:SetTextColor(Color(120, 120, 120)) examplesLabel:SetText("Примеры: NEW_YEAR2026, SUMMER_SALE, WELCOME100, STREAMER_GIFT") codeEntry = vgui.Create("DTextEntry", createPanel) codeEntry:SetPos(20, yPos + 40) codeEntry:SetSize(510, 35) codeEntry:SetFont("F4Menu_Item") codeEntry:SetPlaceholderText("Будет сгенерирован автоматически") codeEntry:SetEnabled(false) codeEntry:SetUpdateOnType(true) codeEntry.Paint = function(s, w, h) local bgColor = s:IsEnabled() and Color(35, 35, 38) or Color(25, 25, 28) draw.RoundedBox(6, 0, 0, w, h, bgColor) s:DrawTextEntryText(Color(255, 255, 255), Color(100, 150, 255), Color(255, 255, 255)) -- Валидация в реальном времени if s:IsEnabled() and s:GetValue() ~= "" then local value = s:GetValue() local isValid = string.match(value, "^[A-Z0-9_]+$") ~= nil if not isValid then draw.RoundedBox(6, 0, 0, w, h, Color(80, 20, 20, 50)) draw.SimpleText("Только A-Z, 0-9 и _", "F4Menu_InfoSmall", w - 10, h/2, Color(255, 100, 100), TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) end end end codeEntry.OnTextChanged = function(s) -- Автоматическое приведение к верхнему регистру local text = s:GetValue() local upperText = string.upper(text) if text ~= upperText then s:SetValue(upperText) s:SetCaretPos(#upperText) end end yPos = yPos + 80 -- Количество IGS local amountLabel = vgui.Create("DLabel", createPanel) amountLabel:SetPos(20, yPos) amountLabel:SetSize(200, 25) amountLabel:SetFont("F4Menu_Item") amountLabel:SetTextColor(Color(200, 200, 200)) amountLabel:SetText("Количество IGS:") local amountEntry = vgui.Create("DTextEntry", createPanel) amountEntry:SetPos(20, yPos + 30) amountEntry:SetSize(510, 35) amountEntry:SetFont("F4Menu_Item") amountEntry:SetPlaceholderText("100") amountEntry:SetNumeric(true) amountEntry.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, Color(35, 35, 38)) s:DrawTextEntryText(Color(255, 255, 255), Color(100, 150, 255), Color(255, 255, 255)) end yPos = yPos + 80 -- Максимум использований local usesLabel = vgui.Create("DLabel", createPanel) usesLabel:SetPos(20, yPos) usesLabel:SetSize(200, 25) usesLabel:SetFont("F4Menu_Item") usesLabel:SetTextColor(Color(200, 200, 200)) usesLabel:SetText("Максимум использований:") local usesEntry = vgui.Create("DTextEntry", createPanel) usesEntry:SetPos(20, yPos + 30) usesEntry:SetSize(510, 35) usesEntry:SetFont("F4Menu_Item") usesEntry:SetPlaceholderText("1") usesEntry:SetNumeric(true) usesEntry.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, Color(35, 35, 38)) s:DrawTextEntryText(Color(255, 255, 255), Color(100, 150, 255), Color(255, 255, 255)) end yPos = yPos + 80 -- Срок действия (дни) local daysLabel = vgui.Create("DLabel", createPanel) daysLabel:SetPos(20, yPos) daysLabel:SetSize(200, 25) daysLabel:SetFont("F4Menu_Item") daysLabel:SetTextColor(Color(200, 200, 200)) daysLabel:SetText("Срок действия (дней):") local daysEntry = vgui.Create("DTextEntry", createPanel) daysEntry:SetPos(20, yPos + 30) daysEntry:SetSize(510, 35) daysEntry:SetFont("F4Menu_Item") daysEntry:SetPlaceholderText("30") daysEntry:SetNumeric(true) daysEntry.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, Color(35, 35, 38)) s:DrawTextEntryText(Color(255, 255, 255), Color(100, 150, 255), Color(255, 255, 255)) end yPos = yPos + 80 -- Доступные ранги local ranksLabel = vgui.Create("DLabel", createPanel) ranksLabel:SetPos(20, yPos) ranksLabel:SetSize(300, 25) ranksLabel:SetFont("F4Menu_Item") ranksLabel:SetTextColor(Color(200, 200, 200)) ranksLabel:SetText("Доступно для рангов (пусто = всем):") local ranksEntry = vgui.Create("DTextEntry", createPanel) ranksEntry:SetPos(20, yPos + 30) ranksEntry:SetSize(510, 35) ranksEntry:SetFont("F4Menu_Item") ranksEntry:SetPlaceholderText("user, vip, moderator (через запятую)") ranksEntry.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, Color(35, 35, 38)) s:DrawTextEntryText(Color(255, 255, 255), Color(100, 150, 255), Color(255, 255, 255)) end yPos = yPos + 80 -- Кнопка создания local createBtn = vgui.Create("DButton", createPanel) createBtn:SetPos(20, yPos) createBtn:SetSize(510, 45) createBtn:SetText("") createBtn.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, s:IsHovered() and Color(1, 87, 39) or Color(1, 67, 29)) draw.SimpleText("СОЗДАТЬ ПРОМОКОД", "F4Menu_Category", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end createBtn.DoClick = function() local code = codeEntry:GetValue() local amount = tonumber(amountEntry:GetValue()) or 100 local maxUses = tonumber(usesEntry:GetValue()) or 1 local days = tonumber(daysEntry:GetValue()) or 30 local expiresAt = os.time() + (days * 86400) local ranksText = ranksEntry:GetValue() local allowedRanks = {} if ranksText ~= "" then for rank in string.gmatch(ranksText, "([^,]+)") do table.insert(allowedRanks, string.Trim(rank)) end end net.Start("ixPromoCodeCreate") net.WriteString(code) net.WriteUInt(amount, 32) net.WriteUInt(maxUses, 16) net.WriteUInt(expiresAt, 32) net.WriteUInt(#allowedRanks, 8) for _, rank in ipairs(allowedRanks) do net.WriteString(rank) end net.SendToServer() -- Очистка полей codeEntry:SetValue("") amountEntry:SetValue("") usesEntry:SetValue("") daysEntry:SetValue("") ranksEntry:SetValue("") end -- Список промокодов local listPanel = vgui.Create("DPanel", frame) listPanel:SetPos(590, 70) listPanel:SetSize(590, 600) listPanel.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, Color(25, 25, 28)) draw.SimpleText("АКТИВНЫЕ ПРОМОКОДЫ", "F4Menu_Category", w/2, 20, Color(200, 200, 200), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end local listScroll = vgui.Create("DScrollPanel", listPanel) listScroll:SetPos(10, 50) listScroll:SetSize(570, 540) local vbar = listScroll:GetVBar() vbar:SetWide(8) function vbar:Paint(w, h) draw.RoundedBox(4, 0, 0, w, h, Color(15, 15, 17)) end function vbar.btnUp:Paint(w, h) end function vbar.btnDown:Paint(w, h) end function vbar.btnGrip:Paint(w, h) draw.RoundedBox(4, 0, 0, w, h, Color(1, 67, 29)) end local function RefreshList() listScroll:Clear() for _, promo in ipairs(self.promoCodesList) do local promoPanel = vgui.Create("DPanel", listScroll) promoPanel:Dock(TOP) promoPanel:DockMargin(5, 5, 5, 0) promoPanel:SetTall(100) promoPanel.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, Color(35, 35, 38)) -- Код draw.SimpleText(promo.code, "F4Menu_Category", 15, 15, Color(100, 150, 255), TEXT_ALIGN_LEFT) -- Информация local info = string.format("IGS: %d | Использований: %d/%d", promo.amount, promo.currentUses, promo.maxUses) draw.SimpleText(info, "F4Menu_Item", 15, 40, Color(200, 200, 200), TEXT_ALIGN_LEFT) -- Срок действия local expiresDate = os.date("%d.%m.%Y %H:%M", promo.expiresAt) local expired = os.time() > promo.expiresAt draw.SimpleText("До: " .. expiresDate, "F4Menu_InfoSmall", 15, 65, expired and Color(255, 100, 100) or Color(150, 150, 150), TEXT_ALIGN_LEFT) -- Ранги if promo.allowedRanks and #promo.allowedRanks > 0 then local ranksText = "Ранги: " .. table.concat(promo.allowedRanks, ", ") draw.SimpleText(ranksText, "F4Menu_InfoSmall", 15, 80, Color(150, 150, 150), TEXT_ALIGN_LEFT) end end -- Кнопка удаления local deleteBtn = vgui.Create("DButton", promoPanel) deleteBtn:SetPos(promoPanel:GetWide() + 385, 30) deleteBtn:SetSize(100, 40) deleteBtn:SetText("") deleteBtn.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, s:IsHovered() and Color(200, 50, 50) or Color(150, 40, 40)) draw.SimpleText("Удалить", "F4Menu_Item", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end deleteBtn.DoClick = function() Derma_Query( "Удалить промокод '" .. promo.code .. "'?", "Подтверждение", "Да", function() net.Start("ixPromoCodeDelete") net.WriteString(promo.code) net.SendToServer() timer.Simple(0.5, RefreshList) end, "Нет" ) end end end -- Обновляем список каждую секунду timer.Create("PromoCodesRefresh", 1, 0, function() if not IsValid(frame) then timer.Remove("PromoCodesRefresh") return end RefreshList() end) RefreshList() end -- Команда для открытия админ-панели concommand.Add("promocodes_admin", function() local plugin = ix.plugin.Get("promocodes") if plugin then plugin:OpenPromoCodesAdmin() end end)