Files
VnUtest/garrysmod/gamemodes/militaryrp/plugins/f4menu/sv_plugin.lua
2026-03-31 10:27:04 +03:00

668 lines
23 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)