752 lines
30 KiB
Lua
752 lines
30 KiB
Lua
if SERVER then
|
||
util.AddNetworkString("ixArsenalOpen")
|
||
util.AddNetworkString("ixArsenalAction")
|
||
util.AddNetworkString("ixWardrobeOpen")
|
||
util.AddNetworkString("ixWardrobeApply")
|
||
end
|
||
|
||
local PLUGIN = PLUGIN
|
||
local config = PLUGIN.config
|
||
|
||
|
||
|
||
local function IsRecruit(ply)
|
||
local char = ply:GetCharacter()
|
||
if not char then return false end
|
||
local spec = char.GetSpec and char:GetSpec() or 0
|
||
return spec == 1
|
||
end
|
||
|
||
-- Helpers to store complex data as JSON strings because SetData/GetData may not accept tables
|
||
local function _loadJSONData(plugin, key)
|
||
-- plugin:GetData() returns the whole plugin store (or default table). We keep subkeys inside that store.
|
||
local store = plugin:GetData() or {}
|
||
if type(store) ~= "table" then return {} end
|
||
local raw = store[key]
|
||
if raw == nil then return {} end
|
||
if type(raw) == "table" then return raw end
|
||
if type(raw) == "string" then
|
||
local ok, tbl = pcall(util.JSONToTable, raw)
|
||
if ok and istable(tbl) then return tbl end
|
||
end
|
||
return {}
|
||
end
|
||
|
||
local function _saveJSONData(plugin, key, tbl)
|
||
if type(tbl) ~= "table" then tbl = {} end
|
||
local store = plugin:GetData() or {}
|
||
if type(store) ~= "table" then store = {} end
|
||
-- store the table directly under the subkey; ix.data.Set will serialize the whole store
|
||
store[key] = tbl
|
||
plugin:SetData(store)
|
||
end
|
||
|
||
-- Faction supply storage helpers
|
||
function PLUGIN:GetFactionSupply(faction)
|
||
local store = _loadJSONData(self, "factionSupply") or {}
|
||
local key = tostring(faction)
|
||
local val = store[faction]
|
||
if val == nil then val = store[key] end
|
||
if val == nil then
|
||
-- fallback to configured startSupply or 0
|
||
return config.startSupply[faction] or 0
|
||
end
|
||
local maxSupply = (self.config and self.config.maxSupply) or 20000
|
||
return math.Clamp(tonumber(val) or 0, 0, maxSupply)
|
||
end
|
||
|
||
-- Returns remaining cooldown seconds for free weapons for a specific player (per-player persistent)
|
||
function PLUGIN:GetFreeWeaponCooldownForPlayer(client)
|
||
if not IsValid(client) or not client:IsPlayer() then return 0 end
|
||
local stamps = _loadJSONData(self, "freeWeaponTimestamps") or {}
|
||
local steam = client:SteamID() or tostring(client:SteamID64() or "unknown")
|
||
local last = tonumber(stamps[steam]) or 0
|
||
local now = os.time()
|
||
local cd = tonumber(config.freeWeaponCooldown) or 0
|
||
if cd <= 0 then return 0 end
|
||
local remain = cd - (now - last)
|
||
if remain < 0 then remain = 0 end
|
||
return math.floor(remain)
|
||
end
|
||
|
||
-- Returns remaining cooldown seconds for donate weapons (10 minutes = 600 seconds)
|
||
function PLUGIN:GetDonateWeaponCooldown(client, weaponClass)
|
||
if not IsValid(client) or not client:IsPlayer() then return 0 end
|
||
local stamps = _loadJSONData(self, "donateWeaponTimestamps") or {}
|
||
local steam = client:SteamID() or tostring(client:SteamID64() or "unknown")
|
||
if not stamps[steam] then return 0 end
|
||
local last = tonumber(stamps[steam][weaponClass]) or 0
|
||
local now = os.time()
|
||
local cd = 600 -- 10 минут
|
||
local remain = cd - (now - last)
|
||
if remain < 0 then remain = 0 end
|
||
return math.floor(remain)
|
||
end
|
||
|
||
function PLUGIN:SetDonateWeaponCooldown(client, weaponClass)
|
||
if not IsValid(client) or not client:IsPlayer() then return end
|
||
local stamps = _loadJSONData(self, "donateWeaponTimestamps") or {}
|
||
local steam = client:SteamID() or tostring(client:SteamID64() or "unknown")
|
||
if not stamps[steam] then stamps[steam] = {} end
|
||
stamps[steam][weaponClass] = os.time()
|
||
_saveJSONData(self, "donateWeaponTimestamps", stamps)
|
||
end
|
||
|
||
function PLUGIN:SetFactionSupply(faction, amount)
|
||
local store = _loadJSONData(self, "factionSupply") or {}
|
||
local key = tostring(faction)
|
||
local value = math.max(tonumber(amount) or 0, 0)
|
||
store[key] = value
|
||
store[faction] = value
|
||
_saveJSONData(self, "factionSupply", store)
|
||
end
|
||
|
||
-- Compatibility helper: add (or subtract) supply for a faction
|
||
function PLUGIN:AddFactionSupply(faction, delta)
|
||
local amount = tonumber(delta) or 0
|
||
local cur = tonumber(self:GetFactionSupply(faction)) or 0
|
||
local maxSupply = (self.config and self.config.maxSupply) or 20000
|
||
local new = math.Clamp(cur + amount, 0, maxSupply)
|
||
|
||
if new == cur then return end -- Если ничего не изменилось (например, уперлись в лимит)
|
||
|
||
self:SetFactionSupply(faction, new)
|
||
|
||
-- Логирование изменения снабжения
|
||
local serverlogsPlugin = ix.plugin.list["serverlogs"]
|
||
if (serverlogsPlugin) then
|
||
local factionName = ix.faction.Get(faction).name or tostring(faction)
|
||
if amount > 0 then
|
||
local message = string.format("Фракция '%s' получила +%d очков снабжения (итого: %d)", factionName, amount, new)
|
||
serverlogsPlugin:AddLog("FACTION_SUPPLY_ADD", message, nil, {
|
||
faction = faction,
|
||
factionName = factionName,
|
||
amount = amount,
|
||
total = new
|
||
})
|
||
elseif amount < 0 then
|
||
local message = string.format("Фракция '%s' потратила %d очков снабжения (итого: %d)", factionName, math.abs(amount), new)
|
||
serverlogsPlugin:AddLog("FACTION_SUPPLY_USE", message, nil, {
|
||
faction = faction,
|
||
factionName = factionName,
|
||
amount = math.abs(amount),
|
||
total = new
|
||
})
|
||
end
|
||
end
|
||
|
||
return new
|
||
end
|
||
|
||
-- Получить список доступного оружия для персонажа (заглушка)
|
||
-- Рекомендуется реализовать: читать FACTION.Spec и FACTION.Podr из ix.faction.Get(faction)
|
||
function PLUGIN:GetAvailableWeapons(char)
|
||
local weapons = {}
|
||
local faction = char:GetFaction()
|
||
local factionTable = ix.faction.Get(faction)
|
||
if not factionTable then
|
||
return weapons
|
||
end
|
||
|
||
local allowed = {}
|
||
local specIndex = tonumber(char:GetSpec()) or 1
|
||
if factionTable.Spec and factionTable.Spec[specIndex] and istable(factionTable.Spec[specIndex].weapons) then
|
||
for _, c in ipairs(factionTable.Spec[specIndex].weapons) do
|
||
allowed[c] = true
|
||
end
|
||
end
|
||
|
||
local podrIndex = tonumber(char:GetPodr()) or 1
|
||
if factionTable.Podr and factionTable.Podr[podrIndex] and istable(factionTable.Podr[podrIndex].preset) then
|
||
for _, c in ipairs(factionTable.Podr[podrIndex].preset) do
|
||
allowed[c] = true
|
||
end
|
||
end
|
||
|
||
-- donate weapons from character data (with expiration check)
|
||
local donateWeaponsTimed = char:GetData("donate_weapons_timed", {})
|
||
local donateTable = {}
|
||
if donateWeaponsTimed and istable(donateWeaponsTimed) then
|
||
local now = os.time()
|
||
for wepClass, wepData in pairs(donateWeaponsTimed) do
|
||
if istable(wepData) and wepData.expires and tonumber(wepData.expires) > now then
|
||
donateTable[wepClass] = true
|
||
end
|
||
end
|
||
end
|
||
|
||
-- Поддержка старой системы без срока (если есть)
|
||
local donateList = char:GetData("donate_weapons", false)
|
||
if donateList and istable(donateList) then
|
||
for _, c in ipairs(donateList) do donateTable[c] = true end
|
||
end
|
||
|
||
-- IGS integration: check if player has any weapons bought via IGS
|
||
local client = char:GetPlayer()
|
||
if IsValid(client) then
|
||
if IGS then
|
||
if isfunction(IGS.GetItems) then
|
||
local items = IGS.GetItems()
|
||
for _, ITEM in pairs(items) do
|
||
local uid = (isfunction(ITEM.GetUID) and ITEM:GetUID()) or ITEM.uid
|
||
if not uid then continue end
|
||
|
||
local has = false
|
||
if isfunction(client.HasPurchase) then
|
||
has = client:HasPurchase(uid)
|
||
elseif isfunction(IGS.PlayerHasItem) then
|
||
has = IGS.PlayerHasItem(client, uid)
|
||
end
|
||
|
||
if has then
|
||
-- Try all possible ways IGS stores the weapon class
|
||
local weaponClass = nil
|
||
local weaponCandidates = {
|
||
(isfunction(ITEM.GetWeapon) and ITEM:GetWeapon()),
|
||
(isfunction(ITEM.GetMeta) and ITEM:GetMeta("weapon")),
|
||
ITEM.weapon,
|
||
ITEM.weapon_class,
|
||
ITEM.class,
|
||
ITEM.uniqueID,
|
||
ITEM.val,
|
||
uid
|
||
}
|
||
for _, candidate in ipairs(weaponCandidates) do
|
||
if candidate and candidate ~= "" then
|
||
weaponClass = candidate
|
||
break
|
||
end
|
||
end
|
||
|
||
if weaponClass and type(weaponClass) ~= "string" then
|
||
weaponClass = tostring(weaponClass)
|
||
end
|
||
|
||
if not weaponClass then
|
||
weaponClass = uid
|
||
end
|
||
|
||
if weaponClass then
|
||
-- Check if this class or a similar one exists in config
|
||
if config.weapons[weaponClass] then
|
||
donateTable[weaponClass] = true
|
||
else
|
||
for configClass, _ in pairs(config.weapons) do
|
||
if configClass:find(weaponClass, 1, true) then
|
||
donateTable[configClass] = true
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
-- Build result from config only if class is allowed and exists in config
|
||
local supply = tonumber(self:GetFactionSupply(faction)) or 0
|
||
for class, _ in pairs(allowed) do
|
||
local data = config.weapons[class]
|
||
if not data then continue end -- skip if not configured
|
||
|
||
-- Проверяем есть ли оружие в донате у игрока
|
||
local hasDonate = donateTable[class]
|
||
|
||
if hasDonate then
|
||
-- Если куплено в донате - показываем как донатное (бесплатно, с кулдауном)
|
||
local donateData = table.Copy(data)
|
||
donateData.isDonateVersion = true -- флаг что это донатная версия
|
||
donateData.supplyPrice = 0
|
||
donateData.moneyPrice = 0
|
||
weapons[class] = donateData
|
||
elseif data.donate_only then
|
||
-- Оружие только для доната, но не куплено - не показываем
|
||
continue
|
||
else
|
||
-- Обычное оружие из spec/podr - показываем с ценой из конфига
|
||
if supply <= 0 then
|
||
if (data.supplyPrice or 0) == 0 then weapons[class] = data end
|
||
else
|
||
weapons[class] = data
|
||
end
|
||
end
|
||
end
|
||
|
||
-- Добавляем оружие которое есть ТОЛЬКО в донате (не в spec/podr)
|
||
for wepClass, _ in pairs(donateTable) do
|
||
if not allowed[wepClass] then
|
||
local data = config.weapons[wepClass]
|
||
if data then
|
||
local donateData = table.Copy(data)
|
||
donateData.isDonateVersion = true
|
||
donateData.supplyPrice = 0
|
||
donateData.moneyPrice = 0
|
||
weapons[wepClass] = donateData
|
||
end
|
||
end
|
||
end
|
||
|
||
return weapons
|
||
end
|
||
|
||
-- Заглушки для действий (реализуйте логику по необходимости)
|
||
function PLUGIN:BuyWeapon(client, weaponClass, payMethod)
|
||
local char = client:GetCharacter()
|
||
if not char then return false, "Нет персонажа" end
|
||
local available = self:GetAvailableWeapons(char)
|
||
local data = available[weaponClass]
|
||
if not data then return false, "Оружие недоступно для покупки" end
|
||
payMethod = payMethod or "supply"
|
||
local faction = char:GetFaction()
|
||
|
||
-- Option A: pay by money (Helix)
|
||
if payMethod == "money" then
|
||
local price = data.moneyPrice or 0
|
||
if price == 0 then return false, "Это оружие нельзя купить за деньги" end
|
||
if not char.HasMoney or not char:HasMoney(price) then return false, "Недостаточно денег" end
|
||
char:TakeMoney(price)
|
||
client:Give(weaponClass)
|
||
return true
|
||
end
|
||
|
||
-- Check if this is a donate weapon version
|
||
local isDonateWeapon = (data.isDonateVersion == true)
|
||
|
||
if isDonateWeapon then
|
||
-- Проверяем что оружие уже есть у игрока
|
||
if client:HasWeapon(weaponClass) then
|
||
return false, "У вас уже есть это оружие"
|
||
end
|
||
|
||
-- Проверяем кулдаун для донат-оружия (10 минут)
|
||
local cooldown = self:GetDonateWeaponCooldown(client, weaponClass)
|
||
if cooldown > 0 then
|
||
local minutes = math.floor(cooldown / 60)
|
||
local seconds = cooldown % 60
|
||
return false, string.format("Вы недавно брали это оружие. Подождите %02d:%02d", minutes, seconds)
|
||
end
|
||
|
||
-- Проверяем категорию (донат оружие тоже может иметь категорию)
|
||
local newCat = data.category or ""
|
||
if newCat ~= "" and not newCat:find("^tool") then
|
||
for _, wep in ipairs(client:GetWeapons()) do
|
||
if not IsValid(wep) then continue end
|
||
local wclass = wep:GetClass()
|
||
local wdata = config.weapons[wclass]
|
||
if wdata and wdata.category == newCat then
|
||
return false, "Вы уже имеете оружие этой категории: " .. newCat
|
||
end
|
||
end
|
||
end
|
||
|
||
-- Check max ammo limit for launchers
|
||
if data.maxAmmo then
|
||
local totalAmmo = 0
|
||
if data.ammoType == "PanzerFaust3 Rocket" or data.ammoType == "rpg_round" then
|
||
totalAmmo = client:GetAmmoCount("rpg_round") + client:GetAmmoCount("PanzerFaust3 Rocket")
|
||
else
|
||
totalAmmo = client:GetAmmoCount(data.ammoType or "rpg_round")
|
||
end
|
||
if totalAmmo >= data.maxAmmo then
|
||
return false, "Вы достигли максимального количества снарядов для этого типа оружия (" .. tostring(data.maxAmmo) .. ")"
|
||
end
|
||
end
|
||
|
||
-- Выдаем оружие и ставим кулдаун
|
||
client:Give(weaponClass)
|
||
|
||
-- Reset ammo for launchers to prevent extra ammo
|
||
if data.ammoType then
|
||
client:SetAmmo(0, data.ammoType)
|
||
end
|
||
|
||
self:SetDonateWeaponCooldown(client, weaponClass)
|
||
|
||
-- Логирование выдачи донат-оружия
|
||
end
|
||
|
||
-- Default: pay by supply
|
||
local supplyCost = data.supplyPrice or 0
|
||
local factionSupply = self:GetFactionSupply(faction)
|
||
|
||
-- If weapon is free (supplyCost == 0), enforce per-player persistent cooldown
|
||
if supplyCost == 0 then
|
||
local stamps = _loadJSONData(self, "freeWeaponTimestamps") or {}
|
||
local steam = client:SteamID() or tostring(client:SteamID64() or "unknown")
|
||
local last = tonumber(stamps[steam]) or 0
|
||
local now = os.time()
|
||
local cd = tonumber(config.freeWeaponCooldown) or 0
|
||
if cd > 0 and (now - last) < cd then
|
||
local remain = cd - (now - last)
|
||
return false, "Вы уже брали бесплатное оружие недавно. Подождите " .. tostring(remain) .. " сек."
|
||
end
|
||
-- don't write timestamp yet: write after successful give
|
||
else
|
||
if supplyCost > 0 then
|
||
if factionSupply < supplyCost then return false, "Недостаточно очков снабжения у фракции" end
|
||
self:AddFactionSupply(faction, -supplyCost)
|
||
end
|
||
end
|
||
-- Check category conflict: prevent two weapons from same category
|
||
local newCat = data.category or ""
|
||
if newCat ~= "" and not newCat:find("^tool") then
|
||
for _, wep in ipairs(client:GetWeapons()) do
|
||
if not IsValid(wep) then continue end
|
||
local wclass = wep:GetClass()
|
||
local wdata = config.weapons[wclass]
|
||
if wdata and wdata.category == newCat then
|
||
return false, "Вы уже имеете оружие этой категории: " .. newCat
|
||
end
|
||
end
|
||
end
|
||
|
||
-- Check max ammo limit for launchers
|
||
if data.maxAmmo then
|
||
local totalAmmo = client:GetAmmoCount(data.ammoType or "rpg_round")
|
||
if totalAmmo >= data.maxAmmo then
|
||
return false, "Вы достигли максимального количества снарядов для этого типа оружия (" .. tostring(data.maxAmmo) .. ")"
|
||
end
|
||
end
|
||
-- Check max count limit for weapons
|
||
if data.maxCount and client:HasWeapon(weaponClass) then
|
||
return false, "У вас уже есть это оружие"
|
||
end
|
||
|
||
client:Give(weaponClass)
|
||
|
||
-- Reset ammo for launchers to prevent extra ammo
|
||
if data.ammoType then
|
||
client:SetAmmo(0, data.ammoType)
|
||
end
|
||
-- If free weapon granted, save timestamp per-player
|
||
if supplyCost == 0 then
|
||
local stamps = _loadJSONData(self, "freeWeaponTimestamps") or {}
|
||
local steam = client:SteamID() or tostring(client:SteamID64() or "unknown")
|
||
stamps[steam] = os.time()
|
||
_saveJSONData(self, "freeWeaponTimestamps", stamps)
|
||
end
|
||
|
||
-- Логирование выдачи оружия из арсенала
|
||
local serverlogsPlugin = ix.plugin.list["serverlogs"]
|
||
if (serverlogsPlugin) then
|
||
local weaponName = data.name or weaponClass
|
||
local factionName = ix.faction.Get(faction).name or tostring(faction)
|
||
local message = string.format("%s получил оружие '%s' из арсенала (стоимость: %d снабжения)", client:Nick(), weaponName, supplyCost)
|
||
serverlogsPlugin:AddLog("WEAPON_SPAWN", message, client, {
|
||
weaponClass = weaponClass,
|
||
weaponName = weaponName,
|
||
supplyCost = supplyCost,
|
||
faction = faction,
|
||
factionName = factionName
|
||
})
|
||
end
|
||
|
||
return true
|
||
end
|
||
|
||
function PLUGIN:BuyAmmo(client, ammoType)
|
||
local char = client:GetCharacter()
|
||
if not char then return false, "Нет персонажа" end
|
||
|
||
-- Запрет на покупку патронов для ракетниц
|
||
if ammoType == "rpg_round" or ammoType == "PanzerFaust3 Rocket" then
|
||
return false, "Патроны для этого типа оружия недоступны для покупки"
|
||
end
|
||
|
||
local ammoData = config.ammo[ammoType]
|
||
if not ammoData then return false, "Тип патронов не найден" end
|
||
|
||
local faction = char:GetFaction()
|
||
local supply = self:GetFactionSupply(faction)
|
||
|
||
local price = ammoData.price or 0
|
||
if price > 0 then
|
||
if supply < price then return false, "Недостаточно очков снабжения у фракции" end
|
||
self:AddFactionSupply(faction, -price)
|
||
end
|
||
|
||
client:GiveAmmo(ammoData.amount or 30, ammoType, true)
|
||
return true
|
||
end
|
||
|
||
function PLUGIN:ReturnWeapon(client, weaponClass)
|
||
local char = client:GetCharacter()
|
||
if not char then return false, "Нет персонажа" end
|
||
local data = config.weapons[weaponClass]
|
||
if not data then return false, "Оружие не найдено" end
|
||
|
||
local faction = char:GetFaction()
|
||
-- Remove weapon
|
||
client:StripWeapon(weaponClass)
|
||
|
||
-- Reset ammo for launchers to prevent extra ammo
|
||
if data.ammoType then
|
||
client:SetAmmo(0, data.ammoType)
|
||
end
|
||
|
||
-- Если это донат-оружие, сбрасываем кулдаун
|
||
if data.donate == true then
|
||
local stamps = _loadJSONData(self, "donateWeaponTimestamps") or {}
|
||
local steam = client:SteamID() or tostring(client:SteamID64() or "unknown")
|
||
if stamps[steam] and stamps[steam][weaponClass] then
|
||
stamps[steam][weaponClass] = nil
|
||
_saveJSONData(self, "donateWeaponTimestamps", stamps)
|
||
end
|
||
return true
|
||
end
|
||
|
||
local refund = math.floor((data.supplyPrice or 0) * 0.8)
|
||
if refund > 0 then self:AddFactionSupply(faction, refund) end
|
||
return true
|
||
end
|
||
|
||
function PLUGIN:SaveData()
|
||
local data = self:GetData() or {}
|
||
data.entities = {}
|
||
|
||
for _, entity in ipairs(ents.FindByClass("ix_arsenal")) do
|
||
data.entities[#data.entities + 1] = {
|
||
class = "ix_arsenal",
|
||
pos = entity:GetPos(),
|
||
angles = entity:GetAngles()
|
||
}
|
||
end
|
||
|
||
for _, entity in ipairs(ents.FindByClass("ix_ammobox")) do
|
||
data.entities[#data.entities + 1] = {
|
||
class = "ix_ammobox",
|
||
pos = entity:GetPos(),
|
||
angles = entity:GetAngles()
|
||
}
|
||
end
|
||
|
||
for _, entity in ipairs(ents.FindByClass("ix_wardrobe")) do
|
||
data.entities[#data.entities + 1] = {
|
||
class = "ix_wardrobe",
|
||
pos = entity:GetPos(),
|
||
angles = entity:GetAngles()
|
||
}
|
||
end
|
||
|
||
self:SetData(data)
|
||
end
|
||
|
||
function PLUGIN:LoadData()
|
||
local data = self:GetData() or {}
|
||
local entities = data.entities or data -- fallback to compatibility if no 'entities' key
|
||
|
||
for _, v in ipairs(entities) do
|
||
if not v.class then continue end
|
||
local entity = ents.Create(v.class or "ix_arsenal")
|
||
entity:SetPos(v.pos)
|
||
entity:SetAngles(v.angles)
|
||
entity:Spawn()
|
||
|
||
local phys = entity:GetPhysicsObject()
|
||
if IsValid(phys) then
|
||
phys:EnableMotion(false)
|
||
end
|
||
end
|
||
end
|
||
|
||
-- Обработчик сетевых действий от клиента
|
||
if SERVER then
|
||
net.Receive("ixArsenalAction", function(len, ply)
|
||
local action = net.ReadString()
|
||
local id = net.ReadString()
|
||
local plugin = ix.plugin.list["arsenal"]
|
||
if not plugin then return end
|
||
|
||
if IsRecruit(ply) then
|
||
ply:Notify("Новоприбывший не может пользоваться арсеналом.")
|
||
return
|
||
end
|
||
|
||
if action == "buy_weapon" then
|
||
local method = net.ReadString()
|
||
local ok, msg = plugin:BuyWeapon(ply, id, method)
|
||
if not ok then
|
||
ply:Notify(msg or "Ошибка покупки оружия")
|
||
end
|
||
elseif action == "buy_ammo" then
|
||
local ok, msg = plugin:BuyAmmo(ply, id)
|
||
if not ok then
|
||
ply:Notify(msg or "Ошибка покупки патронов")
|
||
end
|
||
elseif action == "return_weapon" then
|
||
local ok, msg = plugin:ReturnWeapon(ply, id)
|
||
if not ok then
|
||
ply:Notify(msg or "Ошибка возврата оружия")
|
||
end
|
||
elseif action == "buy_armor" then
|
||
local method = net.ReadString()
|
||
local ok, msg = plugin:BuyArmor(ply, id, method)
|
||
if not ok then
|
||
ply:Notify(msg or "Ошибка покупки брони")
|
||
end
|
||
end
|
||
end)
|
||
end
|
||
|
||
-- Buy armor implementation
|
||
function PLUGIN:BuyArmor(client, armorClass, payMethod)
|
||
local char = client:GetCharacter()
|
||
if not char then return false, "Нет персонажа" end
|
||
local aData = config.armor[armorClass]
|
||
if not aData then return false, "Броня не найдена" end
|
||
payMethod = payMethod or "supply"
|
||
|
||
-- Check current armor: Armor() is the player's current armor value
|
||
local curArmor = tonumber(client:Armor()) or 0
|
||
local giveAmount = tonumber(aData.amount) or 0
|
||
if curArmor >= giveAmount then
|
||
return false, "Вы не можете купить броню — у вас уже есть броня этого уровня или выше"
|
||
end
|
||
|
||
local faction = char:GetFaction()
|
||
|
||
-- money path
|
||
if payMethod == "money" then
|
||
local price = aData.moneyPrice or 0
|
||
if price <= 0 then return false, "Эту броню нельзя купить за деньги" end
|
||
if not char.HasMoney or not char:HasMoney(price) then return false, "Недостаточно денег" end
|
||
char:TakeMoney(price)
|
||
client:SetArmor(giveAmount)
|
||
return true
|
||
end
|
||
|
||
-- supply path
|
||
local supplyCost = aData.supplyPrice or 0
|
||
local factionSupply = self:GetFactionSupply(faction)
|
||
if supplyCost > 0 then
|
||
if factionSupply < supplyCost then return false, "Недостаточно очков снабжения у фракции" end
|
||
self:AddFactionSupply(faction, -supplyCost)
|
||
end
|
||
|
||
client:SetArmor(giveAmount)
|
||
return true
|
||
end
|
||
|
||
function PLUGIN:PlayerLoadedCharacter(client, character, currentChar)
|
||
if not IsValid(client) then return end
|
||
timer.Simple(0.5, function()
|
||
if not IsValid(client) or not client:GetCharacter() then return end
|
||
local char = client:GetCharacter()
|
||
local bodygroups = char:GetData("bodygroups", {})
|
||
local skin = char:GetData("skin", 0)
|
||
local patch = char:GetData("patchIndex", 1)
|
||
for idx, value in pairs(bodygroups) do
|
||
client:SetBodygroup(idx, value)
|
||
end
|
||
client:SetSkin(skin)
|
||
if self.config.patchIndex and self.config.patchMaterials then
|
||
local pMat = self.config.patchMaterials[patch]
|
||
if pMat then
|
||
client:SetSubMaterial(self.config.patchIndex, pMat)
|
||
else
|
||
client:SetSubMaterial(self.config.patchIndex, "")
|
||
end
|
||
end
|
||
hook.Run("PostPlayerLoadout", client)
|
||
end)
|
||
end
|
||
|
||
function PLUGIN:PostPlayerLoadout(client)
|
||
local char = client:GetCharacter()
|
||
if not char then return end
|
||
local model = client:GetModel()
|
||
local bgs = char:GetData("bodygroups", {})
|
||
local speedMod = 1
|
||
local maxArmor = 100
|
||
local ammoMod = 1
|
||
local stats = self.config.wardrobeStats and self.config.wardrobeStats[model]
|
||
if stats then
|
||
for idx, val in pairs(bgs) do
|
||
if stats[idx] and stats[idx][val] then
|
||
local st = stats[idx][val]
|
||
if st.speed then speedMod = speedMod * st.speed end
|
||
if st.armor then maxArmor = maxArmor + st.armor end
|
||
if st.ammo then ammoMod = ammoMod * st.ammo end
|
||
end
|
||
end
|
||
end
|
||
client:SetWalkSpeed(ix.config.Get("walkSpeed") * speedMod)
|
||
client:SetRunSpeed(ix.config.Get("runSpeed") * speedMod)
|
||
client:SetMaxArmor(maxArmor)
|
||
char:SetData("ammoMod", ammoMod)
|
||
end
|
||
|
||
function PLUGIN:EntityTakeDamage(target, dmginfo)
|
||
if not target:IsPlayer() then return end
|
||
local char = target:GetCharacter()
|
||
if not char then return end
|
||
local model = target:GetModel()
|
||
local bgs = char:GetData("bodygroups", {})
|
||
local resist = 1
|
||
local stats = self.config.wardrobeStats and self.config.wardrobeStats[model]
|
||
if stats then
|
||
for idx, val in pairs(bgs) do
|
||
if stats[idx] and stats[idx][val] then
|
||
local st = stats[idx][val]
|
||
if st.dmgResist then
|
||
resist = resist * st.dmgResist
|
||
end
|
||
end
|
||
end
|
||
end
|
||
if resist ~= 1 then
|
||
dmginfo:ScaleDamage(resist)
|
||
end
|
||
end
|
||
|
||
function PLUGIN:InitPostEntity()
|
||
-- Уменьшаем задержку до 2 секунд для более быстрой очистки при старте
|
||
timer.Simple(2, function()
|
||
-- Загружаем сырые данные из стора напрямую
|
||
local currentData = _loadJSONData(self, "factionSupply") or {}
|
||
|
||
-- Перебираем все записи в сторе. Это надежнее, чем идти по индексам фракций,
|
||
-- так как это затронет любые старые или "кривые" ключи в базе.
|
||
local changed = false
|
||
for key, value in pairs(currentData) do
|
||
local factionID = tonumber(key)
|
||
local rawVal = tonumber(value) or 0
|
||
|
||
if factionID and rawVal > 5000 then
|
||
local startVal = (self.config.startSupply and self.config.startSupply[factionID]) or 2000
|
||
|
||
-- Устанавливаем новое значение
|
||
currentData[key] = startVal
|
||
currentData[factionID] = startVal
|
||
changed = true
|
||
|
||
print(string.format("[Arsenal] Автосброс при старте: фракция %s, было %d, стало %d", key, rawVal, startVal))
|
||
end
|
||
end
|
||
|
||
if changed then
|
||
_saveJSONData(self, "factionSupply", currentData)
|
||
end
|
||
end)
|
||
end
|
||
|
||
function PLUGIN:PlayerSetHandsModel(client, ent)
|
||
local char = client:GetCharacter()
|
||
if not char then return end
|
||
local model = client:GetModel()
|
||
local bgs = char:GetData("bodygroups", {})
|
||
local chands = self.config.wardrobeCHands and self.config.wardrobeCHands[model]
|
||
if chands then
|
||
for idx, val in pairs(bgs) do
|
||
if chands[idx] and chands[idx][val] then
|
||
local hData = chands[idx][val]
|
||
ent:SetModel(hData.model or "models/weapons/c_arms_cstrike.mdl")
|
||
ent:SetSkin(hData.skin or 0)
|
||
ent:SetBodyGroups(hData.bodygroups or "0000000")
|
||
return true
|
||
end
|
||
end
|
||
end
|
||
end
|