328 lines
11 KiB
Lua
328 lines
11 KiB
Lua
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
|