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