add sborka

This commit is contained in:
2026-03-31 10:27:04 +03:00
commit f5e5f56c84
2345 changed files with 382127 additions and 0 deletions

View 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)

View File

@@ -0,0 +1,8 @@
local PLUGIN = PLUGIN
PLUGIN.name = "Promocodes System"
PLUGIN.author = "MilitaryRP"
PLUGIN.description = "Система промокодов для доната"
ix.util.Include("sv_plugin.lua")
ix.util.Include("cl_plugin.lua")

View File

@@ -0,0 +1,327 @@
local PLUGIN = PLUGIN
util.AddNetworkString("ixPromoCodeActivate")
util.AddNetworkString("ixPromoCodeResult")
util.AddNetworkString("ixPromoCodeCreate")
util.AddNetworkString("ixPromoCodeDelete")
util.AddNetworkString("ixPromoCodeList")
util.AddNetworkString("ixPromoCodeSync")
PLUGIN.promoCodes = PLUGIN.promoCodes or {}
PLUGIN.promoUsage = PLUGIN.promoUsage or {} -- [code] = {[steamID] = true}
-- Путь к файлу сохранения
local dataPath = "militaryrp/promocodes.txt"
-- Сохранение промокодов
function PLUGIN:SavePromoCodes()
local data = {
codes = self.promoCodes,
usage = self.promoUsage
}
file.CreateDir("militaryrp")
file.Write(dataPath, util.TableToJSON(data))
print("[PROMOCODES] Промокоды сохранены")
end
-- Загрузка промокодов
function PLUGIN:LoadPromoCodes()
if file.Exists(dataPath, "DATA") then
local jsonData = file.Read(dataPath, "DATA")
local data = util.JSONToTable(jsonData)
if data then
self.promoCodes = data.codes or {}
self.promoUsage = data.usage or {}
print("[PROMOCODES] Загружено " .. table.Count(self.promoCodes) .. " промокодов")
end
else
print("[PROMOCODES] Файл промокодов не найден, создается новый")
end
end
-- Генерация случайного кода
function PLUGIN:GenerateCode(length)
local chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
local code = ""
for i = 1, length do
local rand = math.random(1, #chars)
code = code .. string.sub(chars, rand, rand)
end
return code
end
-- Валидация именного промокода
function PLUGIN:ValidateCustomCode(code)
-- Проверка на пустоту
if not code or code == "" then
return false, "Код не может быть пустым"
end
-- Проверка длины (минимум 3, максимум 32 символа)
if #code < 3 then
return false, "Код должен содержать минимум 3 символа"
end
if #code > 32 then
return false, "Код должен содержать максимум 32 символа"
end
-- Проверка на допустимые символы (только A-Z, 0-9 и _)
if not string.match(code, "^[A-Z0-9_]+$") then
return false, "Код может содержать только буквы A-Z, цифры 0-9 и символ подчеркивания _"
end
return true, "OK"
end
-- Создание промокода
function PLUGIN:CreatePromoCode(admin, codeText, amount, maxUses, expiresAt, allowedRanks)
if not IsValid(admin) then return false, "Недействительный игрок" end
-- Проверка прав
if not admin:IsSuperAdmin() then
return false, "У вас нет прав для создания промокодов"
end
-- Генерация кода если не указан
if not codeText or codeText == "" then
codeText = self:GenerateCode(8)
print(string.format("[PROMOCODES] Сгенерирован случайный промокод: %s", codeText))
else
-- Валидация именного кода
local valid, error = self:ValidateCustomCode(codeText)
if not valid then
return false, error
end
print(string.format("[PROMOCODES] Создается именной промокод: %s", codeText))
end
codeText = string.upper(codeText)
-- Проверка существования
if self.promoCodes[codeText] then
return false, "Промокод с таким кодом уже существует"
end
-- Создание промокода
self.promoCodes[codeText] = {
code = codeText,
amount = math.max(1, tonumber(amount) or 100),
maxUses = math.max(1, tonumber(maxUses) or 1),
currentUses = 0,
expiresAt = tonumber(expiresAt) or (os.time() + 86400 * 30), -- По умолчанию 30 дней
allowedRanks = allowedRanks or {}, -- Пустой массив = доступно всем
createdBy = admin:SteamID(),
createdAt = os.time()
}
self.promoUsage[codeText] = {}
self:SavePromoCodes()
return true, "Промокод '" .. codeText .. "' создан на " .. self.promoCodes[codeText].amount .. " IGS"
end
-- Удаление промокода
function PLUGIN:DeletePromoCode(admin, codeText)
if not IsValid(admin) then return false, "Недействительный игрок" end
if not admin:IsSuperAdmin() then
return false, "У вас нет прав для удаления промокодов"
end
codeText = string.upper(codeText)
if not self.promoCodes[codeText] then
return false, "Промокод не найден"
end
self.promoCodes[codeText] = nil
self.promoUsage[codeText] = nil
self:SavePromoCodes()
return true, "Промокод '" .. codeText .. "' удален"
end
-- Проверка доступности промокода для игрока
function PLUGIN:CanUsePromoCode(client, codeText)
local promo = self.promoCodes[codeText]
if not promo then
return false, "Промокод не найден"
end
local steamID = client:SteamID()
-- Проверка использования игроком
if self.promoUsage[codeText] and self.promoUsage[codeText][steamID] then
return false, "Вы уже использовали этот промокод"
end
-- Проверка лимита использований
if promo.currentUses >= promo.maxUses then
return false, "Промокод исчерпан"
end
-- Проверка срока действия
if os.time() > promo.expiresAt then
return false, "Срок действия промокода истек"
end
-- Проверка доступных рангов
if promo.allowedRanks and #promo.allowedRanks > 0 then
local playerRank = client:GetUserGroup()
local hasAccess = false
for _, rank in ipairs(promo.allowedRanks) do
if playerRank == rank then
hasAccess = true
break
end
end
if not hasAccess then
return false, "Этот промокод недоступен для вашего ранга"
end
end
return true, "OK"
end
-- Активация промокода
function PLUGIN:ActivatePromoCode(client, codeText)
if not IsValid(client) then return false, "Недействительный игрок" end
codeText = string.upper(string.Trim(codeText))
if codeText == "" then
return false, "Введите промокод"
end
local canUse, reason = self:CanUsePromoCode(client, codeText)
if not canUse then
return false, reason
end
local promo = self.promoCodes[codeText]
local steamID = client:SteamID()
-- Начисление валюты через F4 плагин
local f4Plugin = ix.plugin.Get("f4menu")
if f4Plugin and f4Plugin.AdjustIGSBalance then
local success, error = f4Plugin:AdjustIGSBalance(client, promo.amount)
if not success then
return false, "Ошибка начисления валюты: " .. (error or "неизвестная ошибка")
end
else
return false, "Система доната недоступна"
end
-- Обновление данных промокода
promo.currentUses = promo.currentUses + 1
if not self.promoUsage[codeText] then
self.promoUsage[codeText] = {}
end
self.promoUsage[codeText][steamID] = true
self:SavePromoCodes()
-- Лог
print(string.format("[PROMOCODES] %s активировал промокод '%s' (+%d IGS)", client:Name(), codeText, promo.amount))
return true, string.format("Промокод активирован! Вы получили %d IGS", promo.amount)
end
-- Загрузка при старте
function PLUGIN:InitializedPlugins()
self:LoadPromoCodes()
end
-- Сетевые обработчики
net.Receive("ixPromoCodeActivate", function(len, client)
local code = net.ReadString()
local plugin = ix.plugin.Get("promocodes")
if not plugin then return end
local success, message = plugin:ActivatePromoCode(client, code)
net.Start("ixPromoCodeResult")
net.WriteBool(success)
net.WriteString(message)
net.Send(client)
end)
net.Receive("ixPromoCodeCreate", function(len, client)
if not client:IsSuperAdmin() then return end
local code = net.ReadString()
local amount = net.ReadUInt(32)
local maxUses = net.ReadUInt(16)
local expiresAt = net.ReadUInt(32)
local ranksCount = net.ReadUInt(8)
local allowedRanks = {}
for i = 1, ranksCount do
table.insert(allowedRanks, net.ReadString())
end
local plugin = ix.plugin.Get("promocodes")
if not plugin then return end
local success, message = plugin:CreatePromoCode(client, code, amount, maxUses, expiresAt, allowedRanks)
net.Start("ixPromoCodeResult")
net.WriteBool(success)
net.WriteString(message)
net.Send(client)
if success then
-- Отправляем обновленный список
plugin:SyncPromoCodesList(client)
end
end)
net.Receive("ixPromoCodeDelete", function(len, client)
if not client:IsSuperAdmin() then return end
local code = net.ReadString()
local plugin = ix.plugin.Get("promocodes")
if not plugin then return end
local success, message = plugin:DeletePromoCode(client, code)
net.Start("ixPromoCodeResult")
net.WriteBool(success)
net.WriteString(message)
net.Send(client)
if success then
plugin:SyncPromoCodesList(client)
end
end)
net.Receive("ixPromoCodeList", function(len, client)
if not client:IsSuperAdmin() then return end
local plugin = ix.plugin.Get("promocodes")
if not plugin then return end
plugin:SyncPromoCodesList(client)
end)
-- Синхронизация списка промокодов
function PLUGIN:SyncPromoCodesList(client)
if not IsValid(client) or not client:IsSuperAdmin() then return end
local codesList = {}
for code, data in pairs(self.promoCodes) do
table.insert(codesList, data)
end
net.Start("ixPromoCodeSync")
net.WriteString(util.TableToJSON(codesList))
net.Send(client)
end