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