412 lines
16 KiB
Lua
412 lines
16 KiB
Lua
-- Клиентские данные
|
||
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)
|