add sborka
This commit is contained in:
411
garrysmod/gamemodes/militaryrp/plugins/promocodes/cl_plugin.lua
Normal file
411
garrysmod/gamemodes/militaryrp/plugins/promocodes/cl_plugin.lua
Normal file
@@ -0,0 +1,411 @@
|
||||
-- Клиентские данные
|
||||
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)
|
||||
Reference in New Issue
Block a user