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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,253 @@
local PLUGIN = PLUGIN
PLUGIN.donateCatalog = PLUGIN.donateCatalog or {
{
id = "currency",
name = "Рубли",
tagline = "Пополнение игрового баланса",
accent = { r = 1, g = 104, b = 44 },
items = {
{
id = "money_1000",
title = "1.000 ₽",
price = 50,
currency = "IGS",
perks = {
"Пополнение игрового баланса",
"Мгновенное зачисление",
"Использование в магазинах"
},
reward = {
type = "money",
amount = 1000
}
},
{
id = "money_5000",
title = "5.000 ₽",
price = 225,
currency = "IGS",
perks = {
"Пополнение игрового баланса",
"Мгновенное зачисление",
"Использование в магазинах",
"Выгодно: +10% бонус"
},
tag = "ВЫГОДНО",
reward = {
type = "money",
amount = 5000
}
},
{
id = "money_10000",
title = "10.000 ₽",
price = 400,
currency = "IGS",
perks = {
"Пополнение игрового баланса",
"Мгновенное зачисление",
"Использование в магазинах",
"Выгодно: +20% бонус"
},
tag = "ХИТ",
reward = {
type = "money",
amount = 10000
}
},
{
id = "money_25000",
title = "25.000 ₽",
price = 900,
currency = "IGS",
perks = {
"Пополнение игрового баланса",
"Мгновенное зачисление",
"Использование в магазинах",
"Максимально выгодно: +28% бонус"
},
tag = "ЛУЧШЕЕ",
reward = {
type = "money",
amount = 25000
}
}
}
},
{
id = "weapons",
name = "Оружие",
tagline = "Донатное оружие в ваш арсенал",
accent = { r = 1, g = 104, b = 44 },
items = {
{ id = "ak12", title = "AK-12", price1Month = 1200, price3Month = 3200, currency = "IGS", perks = { "Доступен в арсенале", "Кулдаун 10 минут", "Любая фракция" }, reward = { type = "weapon", weaponClass = "tacrp_ak_ak12", name = "AK-12" } },
{ id = "mdr", title = "Desert Tech MDR", price1Month = 1500, price3Month = 4000, currency = "IGS", perks = { "Доступен в арсенале", "Кулдаун 10 минут", "Любая фракция" }, reward = { type = "weapon", weaponClass = "arc9_eft_mdr", name = "MDR" } },
{ id = "mp7a1", title = "MP7A1", price1Month = 1300, price3Month = 3500, currency = "IGS", perks = { "Доступен в арсенале", "Кулдаун 10 минут", "Любая фракция" }, reward = { type = "weapon", weaponClass = "arc9_eft_mp7a1", name = "MP7A1" } },
{ id = "ai_axmc", title = "AI AXMC", price1Month = 2000, price3Month = 5500, currency = "IGS", perks = { "Снайперская винтовка", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_ai_axmc", name = "AI AXMC" } },
{ id = "t5000", title = "T-5000", price1Month = 1900, price3Month = 5200, currency = "IGS", perks = { "Снайперская винтовка", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_t5000", name = "T-5000" } },
{ id = "saiga12k", title = "Сайга-12К", price1Month = 1400, price3Month = 3800, currency = "IGS", perks = { "Автоматический дробовик", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_saiga12k", name = "Сайга-12К" } },
{ id = "deagle", title = "Desert Eagle XIX", price1Month = 800, price3Month = 2200, currency = "IGS", perks = { "Мощный пистолет", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_deagle_xix", name = "Desert Eagle" } },
{ id = "apb", title = "АПБ", price1Month = 700, price3Month = 1900, currency = "IGS", perks = { "Бесшумный пистолет", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_apb", name = "АПБ" } },
{ id = "spear", title = "SPEAR", price1Month = 1600, price3Month = 4400, currency = "IGS", perks = { "Доступен в арсенале", "Кулдаун 10 минут", "Любая фракция" }, reward = { type = "weapon", weaponClass = "arc9_eft_spear", name = "SPEAR" } },
{ id = "sr25", title = "SR-25", price1Month = 1800, price3Month = 4900, currency = "IGS", perks = { "Снайперская винтовка", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_sr25", name = "SR-25" } },
{ id = "dvl10", title = "ДВЛ-10", price1Month = 1900, price3Month = 5200, currency = "IGS", perks = { "Снайперская винтовка", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_dvl10", name = "ДВЛ-10" } },
{ id = "hultafors", title = "Dead Blow Hammer", price1Month = 300, price3Month = 800, currency = "IGS", perks = { "Холодное оружие", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_melee_hultafors", name = "Hammer" } },
{ id = "cultist", title = "Cultist Knife", price1Month = 400, price3Month = 1100, currency = "IGS", perks = { "Холодное оружие", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_melee_cultist", name = "Cultist Knife" } },
{ id = "akula", title = "Akula", price1Month = 350, price3Month = 950, currency = "IGS", perks = { "Холодное оружие", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_melee_akula", name = "Akula" } },
{ id = "crash", title = "Crash Axe", price1Month = 350, price3Month = 950, currency = "IGS", perks = { "Холодное оружие", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_melee_crash", name = "Crash Axe" } },
{ id = "kukri", title = "Kukri", price1Month = 400, price3Month = 1100, currency = "IGS", perks = { "Холодное оружие", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_melee_kukri", name = "Kukri" } },
{ id = "ak50", title = "AK-50", price1Month = 2200, price3Month = 6000, currency = "IGS", perks = { "Мощная штурмовая винтовка", "Доступен в арсенале", "Кулдаун 10 минут" }, tag = "ХИТ", reward = { type = "weapon", weaponClass = "arc9_eft_ak50", name = "AK-50" } },
{ id = "vss", title = "ВСС Винторез", price1Month = 1500, price3Month = 4100, currency = "IGS", perks = { "Бесшумная винтовка", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_vss", name = "ВСС" } },
{ id = "mr43", title = "МР-43", price1Month = 1000, price3Month = 2700, currency = "IGS", perks = { "Дробовик", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_mr43", name = "МР-43" } },
{ id = "rpk16", title = "РПК-16", price1Month = 1700, price3Month = 4600, currency = "IGS", perks = { "Ручной пулемёт", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_rpk16", name = "РПК-16" } },
{ id = "rshg2", title = "РШГ-2", price1Month = 2500, price3Month = 6800, currency = "IGS", perks = { "Гранатомёт", "Доступен в арсенале", "Кулдаун 10 минут" }, tag = "РЕДКОЕ", reward = { type = "weapon", weaponClass = "arc9_eft_rshg2", name = "РШГ-2" } },
{ id = "ash12", title = "АШ-12", price1Month = 1800, price3Month = 4900, currency = "IGS", perks = { "Штурмовая винтовка", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_ash12", name = "АШ-12" } },
{ id = "rd704", title = "RD-704", price1Month = 1400, price3Month = 3800, currency = "IGS", perks = { "Штурмовая винтовка", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_rd704", name = "RD-704" } },
{ id = "ppsh41", title = "ППШ-41", price1Month = 900, price3Month = 2400, currency = "IGS", perks = { "Легендарный ПП", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_ppsh41", name = "ППШ-41" } }
}
},
{
id = "other",
name = "Другое",
tagline = "Дополнительные возможности",
accent = { r = 1, g = 104, b = 44 },
items = {
{
id = "voice_chat",
title = "Говорилка",
price = 500,
currency = "IGS",
perks = {
"Разблокировка голосового чата",
"Радиус слышимости 200м",
"Без ограничений по времени",
"Мгновенная активация"
},
reward = {
type = "voice_chat",
successMessage = "Голосовой чат разблокирован!"
}
}
}
},
{
id = "privileges",
name = "Привелегии",
tagline = "VIP статусы и возможности",
accent = { r = 1, g = 104, b = 44 },
items = {
{
id = "vip",
title = "VIP",
price1Month = 300,
price3Month = 800,
currency = "IGS",
perks = {
"Цветной ник и иконка VIP в чате",
"Приоритет при входе на сервер",
"Скидка 10% в арсенале",
"Доступ к VIP-командам"
},
reward = {
type = "privilege",
tier = "vip"
}
},
{
id = "vip_plus",
title = "VIP+",
price1Month = 500,
price3Month = 1400,
currency = "IGS",
perks = {
"Все преимущества VIP",
"Увеличенная зарплата (+15%)",
"Скидка 15% в арсенале",
"Доступ к уникальным моделям",
"Еженедельный бонус валюты"
},
reward = {
type = "privilege",
tier = "vip_plus"
}
},
{
id = "premium",
title = "Premium",
price1Month = 800,
price3Month = 2200,
currency = "IGS",
perks = {
"Все преимущества VIP+",
"Увеличенная зарплата (+25%)",
"Скидка 20% в арсенале",
"Приоритетный спавн техники",
"Доступ к Premium-командам",
"Уникальные анимации и эффекты"
},
tag = "ХИТ",
reward = {
type = "privilege",
tier = "premium"
}
},
{
id = "sponsor",
title = "Спонсор",
price1Month = 1300,
price3Month = 3600,
currency = "IGS",
perks = {
"Доступ к личному Discord-серверу разработчиков проекта с различной информацией, включая оповещения о свежих обновлениях",
"Увеличенный доход за убийство противника на сервере",
"Быстрый захват точки противника (в 1,5 раза быстрее)",
"Уменьшенное ограничение (по времени) на выкат техники",
"Постоянно действующие скидки в 20% на покупку доната (НЕ ЧЕРЕЗ АВТО-ДОНАТ)",
"Уникальные возможности на сервере",
"Участие в закрытых бета-тестированиях и голосованиях"
},
tag = "ЭКСКЛЮЗИВ",
reward = {
type = "privilege",
tier = "sponsor"
}
}
}
}
}
local function BuildDonateLookup()
PLUGIN.donateItemsByID = {}
for _, category in ipairs(PLUGIN.donateCatalog or {}) do
for _, item in ipairs(category.items or {}) do
PLUGIN.donateItemsByID[item.id] = item
end
end
end
BuildDonateLookup()
function PLUGIN:GetDonateCatalog()
return self.donateCatalog or {}
end
function PLUGIN:GetDonateCategory(identifier)
if not identifier then return end
for _, category in ipairs(self:GetDonateCatalog()) do
if category.id == identifier then
return category
end
end
end
function PLUGIN:GetDonateProduct(identifier)
if not identifier then return end
if not self.donateItemsByID then
BuildDonateLookup()
end
return self.donateItemsByID[identifier]
end

View File

@@ -0,0 +1,36 @@
PLUGIN.name = "F4 Menu"
PLUGIN.author = "RefoselTeamWork"
PLUGIN.description = "F4 Menu for Military RP"
-- Ссылки на социальные сети
PLUGIN.socialLinks = {
discord = "https://discord.gg/paQdrP7aD7",
steam = "https://steamcommunity.com/sharedfiles/filedetails/?id=3630170114",
tiktok = "https://www.tiktok.com/@front.team0?_t=ZS-8zQYdHdzDB1&_r=1",
telegram = "https://t.me/frontteamproject"
}
-- Материалы иконок социальных сетей
PLUGIN.socialIcons = {
discord = "materials/ft_ui/military/vnu/f4menu/icons/discord.png",
steam = "materials/ft_ui/military/vnu/f4menu/icons/steam.png",
tiktok = "materials/ft_ui/military/vnu/f4menu/icons/tiktok.png",
telegram = "materials/ft_ui/military/vnu/f4menu/icons/telegram.png"
}
ix.util.Include("sh_donate_config.lua")
ix.util.Include("sh_thanks_config.lua")
ix.util.Include("sv_plugin.lua")
ix.util.Include("cl_plugin.lua")
ix.command.Add("Donate", {
description = "Открыть меню доната (IGS)",
alias = {"Donat", "Донат"},
OnRun = function(self, client)
if (CLIENT) then
RunConsoleCommand("igs")
else
client:ConCommand("igs")
end
end
})

View File

@@ -0,0 +1,181 @@
local PLUGIN = PLUGIN
PLUGIN.thanksPages = PLUGIN.thanksPages or {
{
id = "founders",
title = "Руководители",
people = {
{
name = "Oleg Zakon",
model = "models/player/gman_high.mdl",
sequence = "pose_standing_01",
description = "Основатель и идейный вдохновитель Front Team. Отвечает за общее развитие проекта, стратегию и ключевые решения. Следит за качеством контента, стабильностью сервера и взаимодействием с сообществом.",
link = "https://steamcommunity.com/profiles/76561198204118180"
},
{
name = "Бугор",
model = "models/player/breen.mdl",
sequence = "pose_standing_01",
description = "Правая рука владельца. Курирует техническую часть, развитие игровых механик и поддержку проекта. Обеспечивает бесперебойную работу сервера и помогает в реализации идей сообщества.",
link = "https://steamcommunity.com/profiles/76561199186141452"
}
}
},
{
id = "developers",
title = "Разработчики",
people = {
{
name = "Биба",
model = "models/player/police.mdl",
sequence = "pose_ducking_01",
description = "Настройщик игровых механик",
link = "https://steamcommunity.com/profiles/76561199189433293"
},
{
name = "Scripty",
model = "models/player/skeleton.mdl",
sequence = "pose_standing_04",
description = "Разработчик",
link = "https://steamcommunity.com/profiles/76561198164572709"
},
{
name = "ItzTomber",
model = "models/player/kleiner.mdl",
sequence = "pose_standing_04",
description = "Разработчик",
link = "https://steamcommunity.com/profiles/76561198341626975"
},
{
name = "Hari",
model = "models/player/magnusson.mdl",
sequence = "pose_standing_02",
description = "Разработчик.",
link = "https://steamcommunity.com/profiles/76561198201651767"
},
{
name = "Refosel",
model = "models/kemono_friends/ezo_red_fox/ezo_red_fox_player.mdl",
sequence = "pose_standing_01",
description = "Главный Разработчик",
link = "https://steamcommunity.com/profiles/76561198393073512"
},
{
name = "Прохор",
model = "models/player/odessa.mdl",
sequence = "pose_standing_02",
description = "Маппер.",
link = "no link"
},
{
name = "Сварщик",
model = "models/player/eli.mdl",
sequence = "pose_standing_04",
description = "Модделер.",
link = "https://steamcommunity.com/profiles/76561198440513870"
},
{
name = "Козырный",
model = "models/player/police.mdl",
sequence = "pose_ducking_01",
description = "Figma дизайнер.",
link = "https://steamcommunity.com/profiles/76561199077227396"
}
}
},
{
id = "helix",
title = "Helix",
people = {
{
name = "NutScript",
model = "models/player/police.mdl",
sequence = "pose_ducking_01",
description = "Спасибо за Helix.",
link = "https://github.com/NutScript/NutScript"
},
{
name = "Alex Grist",
model = "models/player/police.mdl",
sequence = "pose_ducking_01",
description = "Спасибо за Helix.",
link = "https://steamcommunity.com/profiles/76561197979205163"
},
{
name = "Igor Radovanovic",
model = "models/player/combine_soldier.mdl",
sequence = "pose_standing_04",
description = "Спасибо за Helix.",
link = "https://steamcommunity.com/profiles/76561197990111113"
},
{
name = "Jaydawg",
model = "models/player/combine_soldier_prisonguard.mdl",
sequence = "pose_standing_02",
description = "Спасибо за Helix.",
link = "https://steamcommunity.com/profiles/76561197970371430"
},
{
name = "nebulous",
model = "models/player/combine_super_soldier.mdl",
sequence = "pose_standing_01",
description = "Спасибо за Helix.",
link = "https://github.com/NebulousCloud"
},
{
name = "Black Tea",
model = "models/player/combine_soldier_prisonguard.mdl",
sequence = "pose_standing_02",
description = "Спасибо за Helix.",
link = "https://steamcommunity.com/profiles/76561197999893894"
},
{
name = "Rain GBizzle",
model = "models/player/combine_soldier.mdl",
sequence = "pose_standing_04",
description = "Спасибо за Helix.",
link = "https://steamcommunity.com/profiles/76561198036111376"
},
{
name = "Luna",
model = "models/player/police.mdl",
sequence = "pose_ducking_01",
description = "Спасибо за Helix.",
link = "https://steamcommunity.com/profiles/76561197988658543"
},
{
name = "Contributors",
model = "models/player/police.mdl",
sequence = "pose_ducking_01",
description = "Спасибо за Helix.",
link = "https://github.com/NebulousCloud/helix/graphs/contributors"
}
}
}
}
function PLUGIN:GetThanksPages()
return self.thanksPages or {}
end
function PLUGIN:GetThanksPage(identifier)
if not identifier then return end
for _, page in ipairs(self:GetThanksPages()) do
if page.id == identifier then
return page
end
end
end
function PLUGIN:GetThanksPerson(pageID, personName)
local page = self:GetThanksPage(pageID)
if not page then return end
for _, person in ipairs(page.people or {}) do
if person.name == personName then
return person
end
end
end

View File

@@ -0,0 +1,667 @@
local PLUGIN = PLUGIN
util.AddNetworkString("ix.F4_RequestInfo")
util.AddNetworkString("ix.F4_SendInfo")
util.AddNetworkString("ix.F4_DonatePurchase")
util.AddNetworkString("ix.F4_DonatePurchaseResult")
util.AddNetworkString("ixChangeName")
util.AddNetworkString("ixF4UpdateName")
local function GetFactionCounters(factionID, podIndex, specIndex)
local factionOnline, samePod, sameSpec = 0, 0, 0
for _, ply in ipairs(player.GetAll()) do
if not IsValid(ply) then continue end
if ply:Team() ~= factionID then continue end
factionOnline = factionOnline + 1
local char = ply:GetCharacter()
if not char then continue end
if char:GetPodr() == podIndex then
samePod = samePod + 1
end
if char:GetSpec() == specIndex then
sameSpec = sameSpec + 1
end
end
return factionOnline, samePod, sameSpec
end
local function BuildInfoPayload(client)
local char = client:GetCharacter()
if not char then return end
local factionID = char:GetFaction()
local factionTable = ix.faction.Get(factionID)
local podIndex = char:GetPodr()
local specIndex = char:GetSpec()
local rankIndex = char:GetRank() or 1
local factionOnline, samePod, sameSpec = GetFactionCounters(factionID, podIndex, specIndex)
local vehiclesPlugin = ix.plugin.Get("vehicles")
local arsenalPlugin = ix.plugin.Get("arsenal")
local techPoints = 0
if vehiclesPlugin and vehiclesPlugin.GetFactionPoints then
techPoints = vehiclesPlugin:GetFactionPoints(factionID) or 0
end
local supplyPoints = 0
if arsenalPlugin and arsenalPlugin.GetFactionSupply then
supplyPoints = arsenalPlugin:GetFactionSupply(factionID) or 0
end
local activeVehicles = 0
if vehiclesPlugin and vehiclesPlugin.activeVehicles then
for _, data in pairs(vehiclesPlugin.activeVehicles) do
if not istable(data) then continue end
if data.faction == factionID then
activeVehicles = activeVehicles + 1
end
end
end
local subdivisionData = {
index = podIndex,
name = "Не назначено",
preset = {},
specDefault = 1,
members = samePod,
model = "",
skin = 0,
bodygroups = {}
}
local specData = {
index = specIndex,
name = "Не назначено",
weapons = {},
members = sameSpec,
podr = 0
}
local rankData = {
index = rankIndex,
name = "Без звания"
}
if factionTable then
if factionTable.Podr and factionTable.Podr[podIndex] then
local unit = factionTable.Podr[podIndex]
subdivisionData.name = unit.name or subdivisionData.name
subdivisionData.preset = unit.preset or subdivisionData.preset
subdivisionData.specDefault = unit.spec_def or subdivisionData.specDefault
subdivisionData.model = unit.model or subdivisionData.model
subdivisionData.skin = unit.skin or subdivisionData.skin
subdivisionData.bodygroups = unit.bodygroups or subdivisionData.bodygroups
end
if factionTable.Spec and factionTable.Spec[specIndex] then
local spec = factionTable.Spec[specIndex]
specData.name = spec.name or specData.name
specData.weapons = spec.weapons or specData.weapons
specData.podr = spec.podr or specData.podr
end
if factionTable.Ranks and factionTable.Ranks[rankIndex] then
local rank = factionTable.Ranks[rankIndex]
rankData.name = rank[1] or rankData.name
end
end
local factionColor = nil
if factionTable and factionTable.color then
factionColor = { r = factionTable.color.r, g = factionTable.color.g, b = factionTable.color.b }
end
local supplyStatus = "Стабильно"
local minSupply = 0
if arsenalPlugin and arsenalPlugin.config then
minSupply = arsenalPlugin.config.minSupply or 0
end
if supplyPoints <= minSupply then
supplyStatus = "Критически низко"
elseif supplyPoints <= minSupply * 2 and minSupply > 0 then
supplyStatus = "Низко"
end
return {
timestamp = os.time(),
faction = {
id = factionID,
name = factionTable and factionTable.name or "Неизвестно",
description = factionTable and factionTable.description or "",
color = factionColor,
online = factionOnline,
techPoints = techPoints,
supplyPoints = supplyPoints,
supplyStatus = supplyStatus,
activeVehicles = activeVehicles
},
character = {
name = char:GetName(),
money = char:GetMoney(),
id = char:GetID() or 0,
rank = rankData,
subdivision = subdivisionData,
spec = specData
}
}
end
net.Receive("ix.F4_RequestInfo", function(_, client)
if not IsValid(client) then return end
local payload = BuildInfoPayload(client)
if not payload then return end
net.Start("ix.F4_SendInfo")
net.WriteTable(payload)
net.Send(client)
end)
local DONATE_PURCHASE_COOLDOWN = 2
PLUGIN.donatePurchaseCooldowns = PLUGIN.donatePurchaseCooldowns or {}
local function GetCooldownKey(client)
return client:SteamID64() or client:SteamID() or client:EntIndex()
end
function PLUGIN:GetIGSBalance(client)
if not IsValid(client) then return 0 end
if client.IGSFunds then
local ok, funds = pcall(client.IGSFunds, client)
if ok and isnumber(funds) then
return math.max(math.floor(funds), 0)
end
end
if client.GetNetVar then
local funds = client:GetNetVar("igsFunds", 0)
if isnumber(funds) then
return math.max(math.floor(funds), 0)
end
end
return 0
end
function PLUGIN:AdjustIGSBalance(client, amount)
if not IsValid(client) then return false, "Игрок недоступен" end
amount = tonumber(amount) or 0
if amount == 0 then return true end
if not client.AddIGSFunds then
return false, "IGS недоступен"
end
local ok, err = pcall(client.AddIGSFunds, client, amount)
if ok then
return true
end
return false, err or "Ошибка IGS"
end
function PLUGIN:IsDonateOnCooldown(client)
local key = GetCooldownKey(client)
local expires = self.donatePurchaseCooldowns[key] or 0
if CurTime() < expires then
return true
end
end
function PLUGIN:ArmDonateCooldown(client)
local key = GetCooldownKey(client)
self.donatePurchaseCooldowns[key] = CurTime() + DONATE_PURCHASE_COOLDOWN
end
function PLUGIN:CanPurchaseDonateProduct(client, entry)
local char = client:GetCharacter()
if not char then
return false, "Нет активного персонажа"
end
if not entry then
return false, "Предложение не найдено"
end
if entry.oneTime then
local history = char:GetData("donate_purchases", {})
if history[entry.id] then
return false, "Этот набор уже был приобретен"
end
end
local hookResult, hookMessage = hook.Run("CanPlayerBuyDonateProduct", client, entry)
if hookResult == false then
return false, hookMessage or "Покупка отклонена"
end
return true
end
function PLUGIN:GrantDonateItems(client, grantData)
if not grantData then return end
local char = client:GetCharacter()
if not char then return end
if istable(grantData.weapons) and #grantData.weapons > 0 then
local owned = char:GetData("donate_weapons", {})
local changed
for _, class in ipairs(grantData.weapons) do
if isstring(class) and not table.HasValue(owned, class) then
table.insert(owned, class)
changed = true
end
end
if changed then
char:SetData("donate_weapons", owned)
end
end
if istable(grantData.cosmetics) and #grantData.cosmetics > 0 then
local cosmetics = char:GetData("donate_cosmetics", {})
for _, cosmeticID in ipairs(grantData.cosmetics) do
if isstring(cosmeticID) then
cosmetics[cosmeticID] = true
end
end
char:SetData("donate_cosmetics", cosmetics)
end
if istable(grantData.vehicles) and #grantData.vehicles > 0 then
local vehicles = char:GetData("donate_vehicles", {})
for _, certificate in ipairs(grantData.vehicles) do
if isstring(certificate) then
vehicles[#vehicles + 1] = certificate
end
end
char:SetData("donate_vehicles", vehicles)
end
if istable(grantData.boosts) and #grantData.boosts > 0 then
local calls = char:GetData("donate_support_calls", {})
for _, callID in ipairs(grantData.boosts) do
calls[#calls + 1] = callID
end
char:SetData("donate_support_calls", calls)
end
if istable(grantData.items) and #grantData.items > 0 then
local pending = char:GetData("donate_items", {})
for _, itemID in ipairs(grantData.items) do
pending[#pending + 1] = { id = itemID, ts = os.time() }
end
char:SetData("donate_items", pending)
end
end
function PLUGIN:ApplyDonateReward(client, entry, mode)
local char = client:GetCharacter()
if not char then
return false, "Нет активного персонажа"
end
local reward = entry.reward
if not reward then
local hookResult, hookMessage = hook.Run("OnDonateReward", client, entry, mode)
if hookResult ~= nil then
return hookResult, hookMessage
end
return false, "Награда не настроена"
end
local rewardType = reward.type
if rewardType == "privilege" then
local tier = reward.tier or entry.id
local duration = nil
if mode == "3month" then
duration = os.time() + (90 * 86400) -- 90 дней
else
duration = os.time() + (30 * 86400) -- 30 дней (1 месяц)
end
local privileges = char:GetData("donate_privileges", {})
privileges[tier] = {
tier = tier,
expires = duration,
granted = os.time(),
mode = mode or "month"
}
char:SetData("donate_privileges", privileges)
-- Применяем группу через SAM если доступно
if sam then
local groupMap = {
vip = "vip",
vip_plus = "vip_plus",
premium = "premium",
sponsor = "sponsor"
}
local rank = groupMap[tier]
if rank then
local expireTime = mode == "3month" and os.time() + (90 * 86400) or os.time() + (30 * 86400)
sam.player.set_rank(client, rank, expireTime)
end
end
local modeText = mode == "3month" and "на 3 месяца" or "на 1 месяц"
return true, reward.successMessage or ("Привилегия " .. tier .. " активирована " .. modeText)
elseif rewardType == "weapon" then
local weaponClass = reward.weaponClass or reward.class
if not weaponClass then
return false, "Класс оружия не указан"
end
-- Добавляем оружие в список донат-оружия персонажа со сроком действия
local donateWeapons = char:GetData("donate_weapons_timed", {})
if not istable(donateWeapons) then donateWeapons = {} end
-- Вычисляем срок действия
local expireTime
if mode == "3month" then
expireTime = os.time() + (90 * 86400) -- 90 дней
else
expireTime = os.time() + (30 * 86400) -- 30 дней
end
-- Добавляем или обновляем срок
donateWeapons[weaponClass] = {
expires = expireTime,
granted = os.time(),
mode = mode or "1month"
}
char:SetData("donate_weapons_timed", donateWeapons)
local modeText = mode == "3month" and "на 3 месяца" or "на 1 месяц"
return true, reward.successMessage or ("Донат-оружие " .. (reward.name or weaponClass) .. " добавлено в ваш арсенал " .. modeText)
elseif rewardType == "money" then
local amount = reward.amount or 0
if amount > 0 and char.GiveMoney then
char:GiveMoney(math.floor(amount))
return true, reward.successMessage or ("Вам начислено " .. amount .. "")
end
return false, "Ошибка начисления денег"
elseif rewardType == "voice_chat" then
-- Разблокировка голосового чата
char:SetData("voice_chat_unlocked", true)
-- Если есть система SAM, можно добавить флаг
if sam then
-- Можно добавить специальную группу или флаг для голосового чата
end
return true, reward.successMessage or "Голосовой чат разблокирован!"
elseif rewardType == "bundle" then
if reward.money and reward.money > 0 and char.GiveMoney then
char:GiveMoney(math.floor(reward.money))
end
if reward.supply and reward.supply ~= 0 then
local arsenal = ix.plugin.Get("arsenal")
if arsenal and arsenal.AddFactionSupply then
arsenal:AddFactionSupply(client:Team(), reward.supply)
end
end
if reward.techPoints and reward.techPoints ~= 0 then
local vehicles = ix.plugin.Get("vehicles")
if vehicles and vehicles.AddFactionPoints then
vehicles:AddFactionPoints(client:Team(), reward.techPoints)
end
end
if reward.grantedItems then
self:GrantDonateItems(client, reward.grantedItems)
end
return true, reward.successMessage or "Комплект успешно выдан"
elseif rewardType == "pass" then
local tier = reward.tier or entry.id
local duration = math.max(1, tonumber(reward.durationDays) or 30) * 86400
local passes = char:GetData("donate_passes", {})
passes[tier] = {
tier = tier,
expires = os.time() + duration,
bonuses = reward.bonuses or {}
}
char:SetData("donate_passes", passes)
return true, reward.successMessage or "Подписка активирована"
elseif rewardType == "booster" then
local boosterID = reward.boosterID or entry.id
local duration = math.max(1, tonumber(reward.durationHours) or 24) * 3600
local boosters = char:GetData("donate_boosters", {})
boosters[boosterID] = {
expires = os.time() + duration,
multiplier = reward.multiplier or 1.0
}
char:SetData("donate_boosters", boosters)
return true, reward.successMessage or "Бустер активирован"
elseif rewardType == "case" then
local caseID = reward.caseID or entry.id
local cases = char:GetData("donate_cases", {})
cases[caseID] = (cases[caseID] or 0) + 1
char:SetData("donate_cases", cases)
return true, reward.successMessage or "Кейс добавлен в коллекцию"
elseif rewardType == "cosmetic" then
local unlockID = reward.unlockID or entry.id
local cosmetics = char:GetData("donate_cosmetics", {})
cosmetics[unlockID] = true
char:SetData("donate_cosmetics", cosmetics)
return true, reward.successMessage or "Косметика разблокирована"
end
local hookResult, hookMessage = hook.Run("OnDonateReward", client, entry)
if hookResult ~= nil then
return hookResult, hookMessage
end
return false, "Тип награды не поддерживается"
end
function PLUGIN:MarkDonatePurchase(client, entry)
local char = client:GetCharacter()
if not char then return end
local history = char:GetData("donate_purchases", {})
history[entry.id] = (history[entry.id] or 0) + 1
char:SetData("donate_purchases", history)
end
function PLUGIN:SendDonateResult(client, success, message, productID)
if not IsValid(client) then return end
net.Start("ix.F4_DonatePurchaseResult")
net.WriteBool(success and true or false)
net.WriteString(message or "")
net.WriteString(productID or "")
net.Send(client)
end
function PLUGIN:HandleDonatePurchase(client, productID, price, mode)
local entry = self:GetDonateProduct(productID)
local ok, msg = self:CanPurchaseDonateProduct(client, entry)
if not ok then
return false, msg, entry
end
local actualPrice = price or tonumber(entry and entry.price) or 0
if actualPrice <= 0 then
return false, "Цена предложения не настроена", entry
end
local funds = self:GetIGSBalance(client)
if funds < actualPrice then
return false, "Недостаточно средств на балансе", entry
end
local debited, debitError = self:AdjustIGSBalance(client, -actualPrice)
if not debited then
return false, debitError or "Не удалось списать средства", entry
end
local success, rewardMessage = self:ApplyDonateReward(client, entry, mode)
if not success then
self:AdjustIGSBalance(client, actualPrice)
return false, rewardMessage or "Ошибка выдачи награды", entry
end
self:MarkDonatePurchase(client, entry, mode)
hook.Run("PostDonatePurchase", client, entry, actualPrice, mode)
return true, rewardMessage or "Покупка завершена", entry
end
net.Receive("ix.F4_DonatePurchase", function(_, client)
if not IsValid(client) or not client:IsPlayer() then return end
local productID = net.ReadString() or ""
local price = net.ReadUInt(32) or 0
local mode = net.ReadString() or "month"
if productID == "" then return end
if PLUGIN:IsDonateOnCooldown(client) then
PLUGIN:SendDonateResult(client, false, "Слишком частые запросы", productID)
return
end
PLUGIN:ArmDonateCooldown(client)
local success, message, entry = PLUGIN:HandleDonatePurchase(client, productID, price, mode)
local entryID = entry and entry.id or productID
if success then
local priceText = tostring(price)
local modeText = mode == "forever" and " (навсегда)" or " (на месяц)"
print(string.format("[F4 Donate] %s (%s) купил %s%s за %s", client:Name(), client:SteamID(), entryID, modeText, priceText))
else
print(string.format("[F4 Donate] Ошибка покупки %s игроком %s: %s", entryID, client:Name(), tostring(message)))
end
PLUGIN:SendDonateResult(client, success, message, entryID)
end)
-- Admin command to add IGS funds
ix.command.Add("GiveIGS", {
description = "Выдать IGS средства игроку",
CanRun = function(self, client)
local userGroup = string.lower(client:GetUserGroup() or "user")
return client:IsAdmin() or AdminPrivs[userGroup]
end,
arguments = {
ix.type.player,
ix.type.number
},
OnRun = function(self, client, target, amount)
if not IsValid(target) then
return "@invalidTarget"
end
amount = math.floor(tonumber(amount) or 0)
if amount == 0 then
return "Укажите корректную сумму"
end
local success, err = PLUGIN:AdjustIGSBalance(target, amount)
if success then
local action = amount > 0 and "выдал" or "снял"
client:Notify(string.format("Вы %s %d IGS для %s", action, math.abs(amount), target:Name()))
target:Notify(string.format("Администратор %s вам %d IGS", action == "выдал" and "выдал" or "снял у вас", math.abs(amount)))
print(string.format("[IGS Admin] %s (%s) %s %d IGS для %s (%s)",
client:Name(), client:SteamID(), action, math.abs(amount), target:Name(), target:SteamID()))
return ""
else
return err or "Ошибка при изменении баланса"
end
end
})
-- Admin command to check IGS balance
ix.command.Add("CheckIGS", {
description = "Проверить IGS баланс игрока",
CanRun = function(self, client)
local userGroup = string.lower(client:GetUserGroup() or "user")
return client:IsAdmin() or AdminPrivs[userGroup]
end,
arguments = {
ix.type.player
},
OnRun = function(self, client, target)
if not IsValid(target) then
return "@invalidTarget"
end
local balance = PLUGIN:GetIGSBalance(target)
return string.format("Баланс IGS игрока %s: %d руб.", target:Name(), balance)
end
})
net.Receive("ixChangeName", function(len, client)
if not IsValid(client) then return end
local character = client:GetCharacter()
if not character then return end
local newName = net.ReadString()
if not newName or newName == "" then return end
if #newName < 3 or #newName > 50 then
client:Notify("Имя должно содержать от 3 до 50 символов")
return
end
if newName:find("[<>\"\\/]") then
client:Notify("Имя содержит недопустимые символы")
return
end
local oldName = character:GetName()
character:SetName(newName)
client:Notify("Ваше имя успешно изменено на: " .. newName)
net.Start("ixF4UpdateName")
net.WriteString(newName)
net.Send(client)
hook.Run("OnCharacterVarChanged", character, "name", oldName, newName)
end)
-- Фикс выдачи донат-оружия IGS в Helix
hook.Add("PostPlayerLoadout", "IGS_Helix_WeaponLoadoutFix", function(client)
if not IsValid(client) or not IGS then return end
timer.Simple(1.5, function()
if not IsValid(client) or not client:GetCharacter() then return end
if isfunction(IGS.PlayerLoadout) then
IGS.PlayerLoadout(client)
end
if isfunction(IGS.GetItems) and IsValid(client) and client.HasPurchase then
for _, ITEM in pairs(IGS.GetItems()) do
if not ITEM or not isfunction(ITEM.GetUID) then continue end
local weaponClass = (isfunction(ITEM.GetWeapon) and ITEM:GetWeapon()) or ITEM.weapon
if weaponClass and client:HasPurchase(ITEM:GetUID()) then
if not client:HasWeapon(weaponClass) then
client:Give(weaponClass)
end
end
end
end
end)
end)