add sborka
This commit is contained in:
706
garrysmod/gamemodes/militaryrp/plugins/arsenal/cl_plugin.lua
Normal file
706
garrysmod/gamemodes/militaryrp/plugins/arsenal/cl_plugin.lua
Normal file
@@ -0,0 +1,706 @@
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
local COLOR_BG_DARK = Color(3, 5, 4)
|
||||
local COLOR_BG_MEDIUM = Color(8, 12, 10)
|
||||
local COLOR_BG_LIGHT = Color(12, 18, 14)
|
||||
local COLOR_PRIMARY = Color(27, 94, 32)
|
||||
local COLOR_PRIMARY_DARK = Color(15, 60, 18)
|
||||
local COLOR_ACCENT = Color(56, 102, 35)
|
||||
local COLOR_TEXT_PRIMARY = Color(165, 214, 167)
|
||||
local COLOR_TEXT_SECONDARY = Color(102, 187, 106)
|
||||
local COLOR_DANGER = Color(136, 14, 14)
|
||||
local COLOR_WARNING = Color(191, 130, 0)
|
||||
local COLOR_BORDER = Color(15, 60, 18, 60)
|
||||
|
||||
if not draw.Circle then
|
||||
function draw.Circle(x, y, radius, color)
|
||||
local segmentCount = math.max(16, radius)
|
||||
surface.SetDrawColor(color or color_white)
|
||||
|
||||
local circle = {}
|
||||
for i = 0, segmentCount do
|
||||
local angle = math.rad((i / segmentCount) * 360)
|
||||
table.insert(circle, {
|
||||
x = x + math.cos(angle) * radius,
|
||||
y = y + math.sin(angle) * radius
|
||||
})
|
||||
end
|
||||
surface.DrawPoly(circle)
|
||||
end
|
||||
end
|
||||
|
||||
if not surface.DrawCircle then
|
||||
function surface.DrawCircle(x, y, radius, color)
|
||||
draw.Circle(x, y, radius, color)
|
||||
end
|
||||
end
|
||||
local COLOR_TEXT_SECONDARY = Color(174, 213, 129)
|
||||
local COLOR_DANGER = Color(229, 57, 53)
|
||||
local COLOR_WARNING = Color(255, 193, 7)
|
||||
local COLOR_BORDER = Color(46, 125, 50, 100)
|
||||
|
||||
net.Receive("ixArsenalOpen", function()
|
||||
local supply = net.ReadUInt(32)
|
||||
local factionID = net.ReadUInt(8)
|
||||
local weaponsData = net.ReadTable()
|
||||
local weaponHas = net.ReadTable()
|
||||
local armorData = net.ReadTable()
|
||||
local freeRemain = net.ReadUInt(32)
|
||||
|
||||
if not weaponsData or type(weaponsData) ~= "table" then weaponsData = {} end
|
||||
if not weaponHas or type(weaponHas) ~= "table" then weaponHas = {} end
|
||||
|
||||
if IsValid(PLUGIN.menu) then
|
||||
PLUGIN.menu:Remove()
|
||||
end
|
||||
|
||||
local scrW, scrH = ScrW(), ScrH()
|
||||
local frame = vgui.Create("DFrame")
|
||||
frame:SetSize(scrW, scrH)
|
||||
frame:SetPos(0, 0)
|
||||
frame:SetTitle("")
|
||||
frame:SetDraggable(false)
|
||||
frame:ShowCloseButton(false)
|
||||
frame:MakePopup()
|
||||
|
||||
frame.Paint = function(s, w, h)
|
||||
surface.SetDrawColor(COLOR_BG_DARK)
|
||||
surface.DrawRect(0, 0, w, h)
|
||||
|
||||
local gradHeight = 300
|
||||
for i = 0, gradHeight do
|
||||
local alpha = (1 - i/gradHeight) * 40
|
||||
surface.SetDrawColor(COLOR_PRIMARY.r, COLOR_PRIMARY.g, COLOR_PRIMARY.b, alpha)
|
||||
surface.DrawRect(0, i, w, 1)
|
||||
end
|
||||
|
||||
surface.SetDrawColor(COLOR_BG_MEDIUM)
|
||||
surface.DrawRect(0, 0, w, 100)
|
||||
|
||||
surface.SetDrawColor(COLOR_PRIMARY)
|
||||
surface.DrawRect(0, 100, w, 3)
|
||||
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
for i = 0, w, 200 do
|
||||
surface.DrawRect(i, 0, 1, 100)
|
||||
end
|
||||
|
||||
draw.SimpleText("◆ ВОЕННЫЙ АРСЕНАЛ ◆", "ixMenuButtonFont", w/2, 35, COLOR_ACCENT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
draw.SimpleText("СИСТЕМА СНАБЖЕНИЯ", "ixSmallFont", w/2, 68, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
|
||||
local closeBtn = vgui.Create("DButton", frame)
|
||||
closeBtn:SetText("")
|
||||
closeBtn:SetSize(42, 42)
|
||||
closeBtn:SetPos(scrW - 60, 18)
|
||||
closeBtn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and COLOR_DANGER or Color(COLOR_DANGER.r * 0.7, COLOR_DANGER.g * 0.7, COLOR_DANGER.b * 0.7)
|
||||
|
||||
draw.RoundedBox(4, 0, 0, w, h, col)
|
||||
|
||||
surface.SetDrawColor(COLOR_BG_DARK)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
|
||||
surface.SetDrawColor(COLOR_TEXT_PRIMARY)
|
||||
surface.DrawLine(w*0.3, h*0.3, w*0.7, h*0.7)
|
||||
surface.DrawLine(w*0.7, h*0.3, w*0.3, h*0.7)
|
||||
|
||||
if s:IsHovered() then
|
||||
surface.SetDrawColor(255, 255, 255, 30)
|
||||
draw.RoundedBox(4, 0, 0, w, h, Color(255, 255, 255, 30))
|
||||
end
|
||||
end
|
||||
closeBtn.DoClick = function() frame:Close() end
|
||||
|
||||
local infoPanel = vgui.Create("DPanel", frame)
|
||||
infoPanel:SetSize(scrW - 80, 70)
|
||||
infoPanel:SetPos(40, 115)
|
||||
infoPanel.Paint = function(s, w, h)
|
||||
draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_LIGHT)
|
||||
surface.SetDrawColor(COLOR_PRIMARY)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
end
|
||||
|
||||
local supplyBox = vgui.Create("DPanel", infoPanel)
|
||||
supplyBox:SetSize(infoPanel:GetWide() / 2 - 10, infoPanel:GetTall())
|
||||
supplyBox:SetPos(5, 0)
|
||||
supplyBox.Paint = function(s, w, h)
|
||||
draw.SimpleText("ОЧКИ СНАБЖЕНИЯ", "ixSmallFont", 20, 12, COLOR_TEXT_SECONDARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
|
||||
draw.SimpleText(tostring(supply), "ixMenuButtonFont", 20, 32, COLOR_ACCENT, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
|
||||
|
||||
surface.SetDrawColor(COLOR_PRIMARY)
|
||||
draw.NoTexture()
|
||||
surface.DrawCircle(w - 35, h/2, 12, COLOR_PRIMARY)
|
||||
end
|
||||
|
||||
local currencySymbol = "$"
|
||||
if PLUGIN and PLUGIN.config and PLUGIN.config.currencySymbol and factionID then
|
||||
currencySymbol = PLUGIN.config.currencySymbol[factionID] or currencySymbol
|
||||
end
|
||||
|
||||
local function formatTime(seconds)
|
||||
seconds = math.max(0, tonumber(seconds) or 0)
|
||||
if seconds <= 0 then return "Готово" end
|
||||
local h = math.floor(seconds / 3600)
|
||||
local m = math.floor((seconds % 3600) / 60)
|
||||
local s = seconds % 60
|
||||
if h > 0 then
|
||||
return string.format("%02d:%02d:%02d", h, m, s)
|
||||
else
|
||||
return string.format("%02d:%02d", m, s)
|
||||
end
|
||||
end
|
||||
|
||||
local cooldownBox = vgui.Create("DPanel", infoPanel)
|
||||
cooldownBox:SetSize(infoPanel:GetWide() / 2 - 10, infoPanel:GetTall())
|
||||
cooldownBox:SetPos(infoPanel:GetWide() / 2 + 5, 0)
|
||||
cooldownBox.Paint = function(s, w, h)
|
||||
draw.SimpleText("БЕСПЛАТНОЕ СНАРЯЖЕНИЕ", "ixSmallFont", 20, 12, COLOR_TEXT_SECONDARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
|
||||
|
||||
local timeText = formatTime(freeRemain)
|
||||
local timeColor = freeRemain <= 0 and COLOR_PRIMARY or COLOR_WARNING
|
||||
draw.SimpleText(timeText, "ixMenuButtonFont", 20, 32, timeColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
|
||||
|
||||
if freeRemain > 0 then
|
||||
surface.SetDrawColor(COLOR_WARNING)
|
||||
draw.NoTexture()
|
||||
surface.DrawCircle(w - 35, h/2, 12, COLOR_WARNING)
|
||||
else
|
||||
surface.SetDrawColor(COLOR_PRIMARY)
|
||||
draw.NoTexture()
|
||||
surface.DrawCircle(w - 35, h/2, 12, COLOR_PRIMARY)
|
||||
end
|
||||
end
|
||||
|
||||
local colWidth = (scrW - 120) / 2
|
||||
local startY = 205
|
||||
|
||||
-- Панель оружия
|
||||
local weaponsPanel = vgui.Create("DPanel", frame)
|
||||
weaponsPanel:SetSize(colWidth, scrH - startY - 40)
|
||||
weaponsPanel:SetPos(40, startY)
|
||||
weaponsPanel.Paint = function(s, w, h)
|
||||
draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_MEDIUM)
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
end
|
||||
|
||||
local weaponsHeader = vgui.Create("DPanel", weaponsPanel)
|
||||
weaponsHeader:SetSize(weaponsPanel:GetWide(), 45)
|
||||
weaponsHeader:SetPos(0, 0)
|
||||
weaponsHeader.Paint = function(s, w, h)
|
||||
draw.RoundedBoxEx(8, 0, 0, w, h, COLOR_PRIMARY_DARK, true, true, false, false)
|
||||
|
||||
draw.SimpleText("▣ ОРУЖИЕ", "ixSmallFont", 15, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
|
||||
-- Декоративная линия
|
||||
surface.SetDrawColor(COLOR_ACCENT)
|
||||
surface.DrawRect(0, h-2, w, 2)
|
||||
end
|
||||
|
||||
local weaponsScroll = vgui.Create("DScrollPanel", weaponsPanel)
|
||||
weaponsScroll:SetSize(weaponsPanel:GetWide() - 20, weaponsPanel:GetTall() - 60)
|
||||
weaponsScroll:SetPos(10, 50)
|
||||
|
||||
local sbar = weaponsScroll:GetVBar()
|
||||
sbar:SetHideButtons(true)
|
||||
function sbar:Paint(w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_DARK)
|
||||
end
|
||||
function sbar.btnGrip:Paint(w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, COLOR_PRIMARY)
|
||||
end
|
||||
|
||||
-- Функция для создания окна выбора оружия из категории
|
||||
local function OpenCategoryWeapons(categoryName, categoryIcon, categoryWeapons)
|
||||
local catFrame = vgui.Create("DFrame")
|
||||
catFrame:SetSize(800, 600)
|
||||
catFrame:Center()
|
||||
catFrame:SetTitle("")
|
||||
catFrame:SetDraggable(true)
|
||||
catFrame:ShowCloseButton(false)
|
||||
catFrame:MakePopup()
|
||||
|
||||
catFrame.Paint = function(s, w, h)
|
||||
draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_DARK)
|
||||
surface.SetDrawColor(COLOR_PRIMARY)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 3)
|
||||
|
||||
-- Заголовок
|
||||
draw.RoundedBoxEx(8, 0, 0, w, 60, COLOR_PRIMARY_DARK, true, true, false, false)
|
||||
draw.SimpleText(categoryIcon .. " " .. categoryName, "ixMenuButtonFont", w/2, 30, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
|
||||
surface.SetDrawColor(COLOR_ACCENT)
|
||||
surface.DrawRect(0, 60, w, 2)
|
||||
end
|
||||
|
||||
-- Кнопка закрытия
|
||||
local catCloseBtn = vgui.Create("DButton", catFrame)
|
||||
catCloseBtn:SetText("")
|
||||
catCloseBtn:SetSize(35, 35)
|
||||
catCloseBtn:SetPos(catFrame:GetWide() - 45, 12)
|
||||
catCloseBtn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and COLOR_DANGER or Color(COLOR_DANGER.r * 0.7, COLOR_DANGER.g * 0.7, COLOR_DANGER.b * 0.7)
|
||||
draw.RoundedBox(4, 0, 0, w, h, col)
|
||||
surface.SetDrawColor(COLOR_TEXT_PRIMARY)
|
||||
surface.DrawLine(w*0.3, h*0.3, w*0.7, h*0.7)
|
||||
surface.DrawLine(w*0.7, h*0.3, w*0.3, h*0.7)
|
||||
end
|
||||
catCloseBtn.DoClick = function() catFrame:Close() end
|
||||
|
||||
-- Скролл с оружием
|
||||
local catScroll = vgui.Create("DScrollPanel", catFrame)
|
||||
catScroll:SetSize(catFrame:GetWide() - 30, catFrame:GetTall() - 85)
|
||||
catScroll:SetPos(15, 70)
|
||||
|
||||
local catSbar = catScroll:GetVBar()
|
||||
catSbar:SetHideButtons(true)
|
||||
function catSbar:Paint(w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_DARK)
|
||||
end
|
||||
function catSbar.btnGrip:Paint(w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, COLOR_PRIMARY)
|
||||
end
|
||||
|
||||
-- Создаем карточки оружия
|
||||
for class, data in SortedPairsByMemberValue(categoryWeapons, "name", true) do
|
||||
local has = weaponHas[class]
|
||||
local row = vgui.Create("DPanel", catScroll)
|
||||
row:SetSize(catScroll:GetWide() - 10, 110)
|
||||
row:Dock(TOP)
|
||||
row:DockMargin(0, 0, 0, 10)
|
||||
|
||||
local isHovered = false
|
||||
row.Paint = function(s, w, h)
|
||||
local bgColor = has and Color(45, 25, 20) or COLOR_BG_LIGHT
|
||||
if isHovered then
|
||||
bgColor = Color(bgColor.r + 10, bgColor.g + 15, bgColor.b + 10)
|
||||
end
|
||||
draw.RoundedBox(6, 0, 0, w, h, bgColor)
|
||||
|
||||
local statusColor = has and Color(205, 127, 50) or COLOR_PRIMARY
|
||||
draw.RoundedBoxEx(6, 0, 0, 5, h, statusColor, true, false, true, false)
|
||||
|
||||
surface.SetDrawColor(has and Color(205, 127, 50, 80) or COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
|
||||
if isHovered then
|
||||
surface.SetDrawColor(COLOR_ACCENT)
|
||||
surface.DrawRect(5, h-3, w-10, 3)
|
||||
end
|
||||
end
|
||||
|
||||
row.OnCursorEntered = function() isHovered = true end
|
||||
row.OnCursorExited = function() isHovered = false end
|
||||
|
||||
-- Название
|
||||
local nameLabel = vgui.Create("DLabel", row)
|
||||
nameLabel:SetText(data.name or class)
|
||||
nameLabel:SetFont("ixSmallFont")
|
||||
nameLabel:SetTextColor(COLOR_TEXT_PRIMARY)
|
||||
nameLabel:SetPos(15, 10)
|
||||
nameLabel:SizeToContents()
|
||||
|
||||
-- Цена
|
||||
local money = data.moneyPrice or 0
|
||||
local supply = data.supplyPrice or 0
|
||||
local priceLabel = vgui.Create("DLabel", row)
|
||||
local priceText = ""
|
||||
if (money or 0) <= 0 and (supply or 0) <= 0 then
|
||||
priceText = "★ БЕСПЛАТНО"
|
||||
else
|
||||
local parts = {}
|
||||
if money > 0 then table.insert(parts, currencySymbol .. tostring(money)) end
|
||||
if supply > 0 then table.insert(parts, tostring(supply) .. " ОС") end
|
||||
priceText = table.concat(parts, " • ")
|
||||
end
|
||||
priceLabel:SetText(priceText)
|
||||
priceLabel:SetFont("ixSmallFont")
|
||||
priceLabel:SetTextColor(money <= 0 and supply <= 0 and COLOR_ACCENT or COLOR_TEXT_PRIMARY)
|
||||
priceLabel:SetPos(15, 40)
|
||||
priceLabel:SizeToContents()
|
||||
|
||||
-- Донат метка
|
||||
if data.donate then
|
||||
local donateLabel = vgui.Create("DLabel", row)
|
||||
donateLabel:SetText("★ ПРЕМИУМ")
|
||||
donateLabel:SetFont("ixSmallFont")
|
||||
donateLabel:SetTextColor(COLOR_WARNING)
|
||||
donateLabel:SetPos(15, 70)
|
||||
donateLabel:SizeToContents()
|
||||
end
|
||||
|
||||
-- Кнопка действия
|
||||
local btn = vgui.Create("DButton", row)
|
||||
btn:SetSize(120, 40)
|
||||
btn:SetPos(row:GetWide() - 130, 35)
|
||||
btn:SetText("")
|
||||
|
||||
local btnText = has and "◄ ВЕРНУТЬ" or "ВЗЯТЬ ►"
|
||||
local btnColor = has and Color(165, 85, 60) or COLOR_PRIMARY
|
||||
local btnColorHover = has and Color(205, 105, 70) or COLOR_ACCENT
|
||||
|
||||
btn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and btnColorHover or btnColor
|
||||
draw.RoundedBox(6, 0, 0, w, h, col)
|
||||
|
||||
if s:IsHovered() then
|
||||
surface.SetDrawColor(255, 255, 255, 30)
|
||||
draw.RoundedBox(6, 2, 2, w-4, h-4, Color(255, 255, 255, 30))
|
||||
end
|
||||
|
||||
surface.SetDrawColor(COLOR_BG_DARK)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
|
||||
draw.SimpleText(btnText, "ixSmallFont", w/2, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
|
||||
btn.DoClick = function()
|
||||
if has then
|
||||
Derma_Query("Вернуть снаряжение? Фракция получит 80% стоимости.", "Возврат", "Подтвердить", function()
|
||||
net.Start("ixArsenalAction")
|
||||
net.WriteString("return_weapon")
|
||||
net.WriteString(class)
|
||||
net.SendToServer()
|
||||
catFrame:Close()
|
||||
frame:Close()
|
||||
end, "Отмена", function() end)
|
||||
else
|
||||
local options = {}
|
||||
if (data.moneyPrice or 0) > 0 then table.insert(options, {text = "Купить за деньги", method = "money"}) end
|
||||
if (data.supplyPrice or 0) >= 0 then table.insert(options, {text = "Купить за очки", method = "supply"}) end
|
||||
|
||||
if #options == 0 then
|
||||
Derma_Message("Невозможно приобрести это оружие", "Ошибка", "OK")
|
||||
return
|
||||
end
|
||||
|
||||
if #options == 1 then
|
||||
net.Start("ixArsenalAction")
|
||||
net.WriteString("buy_weapon")
|
||||
net.WriteString(class)
|
||||
net.WriteString(options[1].method)
|
||||
net.SendToServer()
|
||||
catFrame:Close()
|
||||
frame:Close()
|
||||
else
|
||||
local query = Derma_Query("Выберите способ оплаты:", "Оплата", options[1].text, function()
|
||||
net.Start("ixArsenalAction")
|
||||
net.WriteString("buy_weapon")
|
||||
net.WriteString(class)
|
||||
net.WriteString(options[1].method)
|
||||
net.SendToServer()
|
||||
catFrame:Close()
|
||||
frame:Close()
|
||||
end, options[2].text, function()
|
||||
net.Start("ixArsenalAction")
|
||||
net.WriteString("buy_weapon")
|
||||
net.WriteString(class)
|
||||
net.WriteString(options[2].method)
|
||||
net.SendToServer()
|
||||
catFrame:Close()
|
||||
frame:Close()
|
||||
end, "Отмена", function() end)
|
||||
|
||||
if IsValid(query) then
|
||||
query:ShowCloseButton(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Панель брони
|
||||
local armorPanel = vgui.Create("DPanel", frame)
|
||||
armorPanel:SetSize(colWidth, scrH - startY - 40)
|
||||
armorPanel:SetPos(scrW - colWidth - 40, startY)
|
||||
armorPanel.Paint = function(s, w, h)
|
||||
draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_MEDIUM)
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
end
|
||||
|
||||
local armorHeader = vgui.Create("DPanel", armorPanel)
|
||||
armorHeader:SetSize(armorPanel:GetWide(), 45)
|
||||
armorHeader:SetPos(0, 0)
|
||||
armorHeader.Paint = function(s, w, h)
|
||||
draw.RoundedBoxEx(8, 0, 0, w, h, COLOR_PRIMARY_DARK, true, true, false, false)
|
||||
|
||||
draw.SimpleText("⬢ ЗАЩИТА", "ixSmallFont", 15, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
|
||||
-- Декоративная линия
|
||||
surface.SetDrawColor(COLOR_ACCENT)
|
||||
surface.DrawRect(0, h-2, w, 2)
|
||||
end
|
||||
|
||||
local armorScroll = vgui.Create("DScrollPanel", armorPanel)
|
||||
armorScroll:SetSize(armorPanel:GetWide() - 20, armorPanel:GetTall() - 60)
|
||||
armorScroll:SetPos(10, 50)
|
||||
|
||||
local sbar2 = armorScroll:GetVBar()
|
||||
sbar2:SetHideButtons(true)
|
||||
function sbar2:Paint(w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_DARK)
|
||||
end
|
||||
function sbar2.btnGrip:Paint(w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, COLOR_PRIMARY)
|
||||
end
|
||||
|
||||
-- Группировка оружия по категориям
|
||||
local categories = {
|
||||
{name = "Основное оружие", icon = "▣", key = "primary", weapons = {}},
|
||||
{name = "Пистолеты", icon = "▢", key = "secondary", weapons = {}},
|
||||
{name = "Гранаты", icon = "◆", key = "grenades", weapons = {}},
|
||||
{name = "Холодное оружие", icon = "▸", key = "melee", weapons = {}},
|
||||
{name = "Прочее", icon = "◇", key = "other", weapons = {}}
|
||||
}
|
||||
|
||||
-- Распределяем оружие по категориям
|
||||
for class, data in pairs(weaponsData) do
|
||||
if type(data) == "table" then
|
||||
local cat = data.category or "other"
|
||||
local added = false
|
||||
|
||||
if cat == "primary" then
|
||||
table.insert(categories[1].weapons, {class = class, data = data})
|
||||
added = true
|
||||
elseif cat == "secondary" then
|
||||
table.insert(categories[2].weapons, {class = class, data = data})
|
||||
added = true
|
||||
elseif cat == "grenade1" or cat == "grenade2" or cat == "grenade" then
|
||||
table.insert(categories[3].weapons, {class = class, data = data})
|
||||
added = true
|
||||
elseif cat == "melee" then
|
||||
table.insert(categories[4].weapons, {class = class, data = data})
|
||||
added = true
|
||||
end
|
||||
|
||||
if not added then
|
||||
table.insert(categories[5].weapons, {class = class, data = data})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Создаем кнопки категорий
|
||||
local totalWeapons = 0
|
||||
for _, category in ipairs(categories) do
|
||||
if #category.weapons > 0 then
|
||||
totalWeapons = totalWeapons + #category.weapons
|
||||
|
||||
local catButton = vgui.Create("DPanel", weaponsScroll)
|
||||
catButton:SetSize(weaponsScroll:GetWide() - 10, 80)
|
||||
catButton:Dock(TOP)
|
||||
catButton:DockMargin(0, 0, 0, 10)
|
||||
catButton:SetCursor("hand")
|
||||
|
||||
local isHovered = false
|
||||
catButton.Paint = function(s, w, h)
|
||||
local bgColor = COLOR_BG_LIGHT
|
||||
if isHovered then
|
||||
bgColor = Color(bgColor.r + 15, bgColor.g + 20, bgColor.b + 15)
|
||||
end
|
||||
draw.RoundedBox(6, 0, 0, w, h, bgColor)
|
||||
|
||||
-- Боковая полоска
|
||||
draw.RoundedBoxEx(6, 0, 0, 6, h, COLOR_PRIMARY, true, false, true, false)
|
||||
|
||||
-- Рамка
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
|
||||
-- Акцентная линия при наведении
|
||||
if isHovered then
|
||||
surface.SetDrawColor(COLOR_ACCENT)
|
||||
surface.DrawRect(6, h-3, w-12, 3)
|
||||
end
|
||||
|
||||
-- Иконка категории
|
||||
draw.SimpleText(category.icon, "ixMenuButtonFont", 30, h/2, COLOR_ACCENT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
|
||||
-- Название категории
|
||||
draw.SimpleText(category.name, "ixSmallFont", 60, h/2 - 12, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
|
||||
-- Количество оружия
|
||||
draw.SimpleText(#category.weapons .. " ед.", "ixSmallFont", 60, h/2 + 12, COLOR_TEXT_SECONDARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
|
||||
-- Стрелка
|
||||
draw.SimpleText("►", "ixSmallFont", w - 25, h/2, COLOR_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
|
||||
catButton.OnCursorEntered = function() isHovered = true end
|
||||
catButton.OnCursorExited = function() isHovered = false end
|
||||
catButton.OnMousePressed = function()
|
||||
-- Подготовка данных для окна категории
|
||||
local categoryWeapons = {}
|
||||
for _, wpn in ipairs(category.weapons) do
|
||||
categoryWeapons[wpn.class] = wpn.data
|
||||
end
|
||||
OpenCategoryWeapons(category.name, category.icon, categoryWeapons)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Если нет оружия вообще
|
||||
if totalWeapons == 0 then
|
||||
local emptyLabel = vgui.Create("DPanel", weaponsScroll)
|
||||
emptyLabel:SetSize(weaponsScroll:GetWide(), 100)
|
||||
emptyLabel:Dock(TOP)
|
||||
emptyLabel:DockMargin(0, 20, 0, 0)
|
||||
emptyLabel.Paint = function(s, w, h)
|
||||
draw.RoundedBox(6, 0, 0, w, h, COLOR_BG_LIGHT)
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
|
||||
draw.SimpleText("⚠ НЕТ ДОСТУПНОГО ОРУЖИЯ", "ixSmallFont", w/2, h/2 - 10, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
draw.SimpleText("Обратитесь к командованию", "ixSmallFont", w/2, h/2 + 15, Color(COLOR_TEXT_SECONDARY.r * 0.7, COLOR_TEXT_SECONDARY.g * 0.7, COLOR_TEXT_SECONDARY.b * 0.7), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
end
|
||||
|
||||
-- Заполнение брони
|
||||
if not armorData or type(armorData) ~= "table" then armorData = {} end
|
||||
if table.Count(armorData) > 0 then
|
||||
for class, data in SortedPairsByMemberValue(armorData, "name", true) do
|
||||
local row = vgui.Create("DPanel", armorScroll)
|
||||
row:SetSize(armorScroll:GetWide() - 10, 100)
|
||||
row:Dock(TOP)
|
||||
row:DockMargin(0, 0, 0, 10)
|
||||
|
||||
local isHovered = false
|
||||
row.Paint = function(s, w, h)
|
||||
local bgColor = COLOR_BG_LIGHT
|
||||
if isHovered then
|
||||
bgColor = Color(bgColor.r + 10, bgColor.g + 15, bgColor.b + 10)
|
||||
end
|
||||
draw.RoundedBox(6, 0, 0, w, h, bgColor)
|
||||
|
||||
-- Боковая полоска
|
||||
draw.RoundedBoxEx(6, 0, 0, 5, h, COLOR_ACCENT, true, false, true, false)
|
||||
|
||||
-- Рамка
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
|
||||
-- Акцентная линия при наведении
|
||||
if isHovered then
|
||||
surface.SetDrawColor(COLOR_ACCENT)
|
||||
surface.DrawRect(5, h-3, w-10, 3)
|
||||
end
|
||||
end
|
||||
|
||||
row.OnCursorEntered = function() isHovered = true end
|
||||
row.OnCursorExited = function() isHovered = false end
|
||||
|
||||
-- Название брони
|
||||
local nameLabel = vgui.Create("DLabel", row)
|
||||
nameLabel:SetText(data.name or class)
|
||||
nameLabel:SetFont("ixSmallFont")
|
||||
nameLabel:SetTextColor(COLOR_TEXT_PRIMARY)
|
||||
nameLabel:SetPos(15, 10)
|
||||
nameLabel:SizeToContents()
|
||||
|
||||
-- Параметры брони
|
||||
local amountLabel = vgui.Create("DLabel", row)
|
||||
amountLabel:SetText("⬢ Защита: " .. tostring(data.amount or 0) .. " ед.")
|
||||
amountLabel:SetFont("ixSmallFont")
|
||||
amountLabel:SetTextColor(COLOR_TEXT_SECONDARY)
|
||||
amountLabel:SetPos(15, 35)
|
||||
amountLabel:SizeToContents()
|
||||
|
||||
-- Цена
|
||||
local moneyP = data.moneyPrice or 0
|
||||
local supplyP = data.supplyPrice or 0
|
||||
local priceText = ""
|
||||
if (moneyP or 0) <= 0 and (supplyP or 0) <= 0 then
|
||||
priceText = "★ БЕСПЛАТНО"
|
||||
else
|
||||
local parts = {}
|
||||
if moneyP > 0 then table.insert(parts, currencySymbol .. tostring(moneyP)) end
|
||||
if supplyP > 0 then table.insert(parts, tostring(supplyP) .. " ОС") end
|
||||
priceText = table.concat(parts, " • ")
|
||||
end
|
||||
local priceLabel = vgui.Create("DLabel", row)
|
||||
priceLabel:SetText(priceText)
|
||||
priceLabel:SetFont("ixSmallFont")
|
||||
priceLabel:SetTextColor(moneyP <= 0 and supplyP <= 0 and COLOR_ACCENT or COLOR_TEXT_PRIMARY)
|
||||
priceLabel:SetPos(15, 58)
|
||||
priceLabel:SizeToContents()
|
||||
|
||||
-- Кнопка покупки
|
||||
local btn = vgui.Create("DButton", row)
|
||||
btn:SetSize(120, 40)
|
||||
btn:SetPos(row:GetWide() - 130, 30)
|
||||
btn:SetText("")
|
||||
|
||||
btn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and COLOR_ACCENT or COLOR_PRIMARY
|
||||
draw.RoundedBox(6, 0, 0, w, h, col)
|
||||
|
||||
if s:IsHovered() then
|
||||
surface.SetDrawColor(255, 255, 255, 30)
|
||||
draw.RoundedBox(6, 2, 2, w-4, h-4, Color(255, 255, 255, 30))
|
||||
end
|
||||
|
||||
surface.SetDrawColor(COLOR_BG_DARK)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
|
||||
draw.SimpleText("КУПИТЬ ►", "ixSmallFont", w/2, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
|
||||
btn.DoClick = function()
|
||||
local options = {}
|
||||
if (data.moneyPrice or 0) > 0 then table.insert(options, {text = "Купить за деньги", method = "money"}) end
|
||||
if (data.supplyPrice or 0) >= 0 then table.insert(options, {text = "Купить за очки", method = "supply"}) end
|
||||
|
||||
if #options == 0 then
|
||||
Derma_Message("Невозможно приобрести эту броню", "Ошибка", "OK")
|
||||
return
|
||||
end
|
||||
|
||||
if #options == 1 then
|
||||
net.Start("ixArsenalAction")
|
||||
net.WriteString("buy_armor")
|
||||
net.WriteString(class)
|
||||
net.WriteString(options[1].method)
|
||||
net.SendToServer()
|
||||
frame:Close()
|
||||
else
|
||||
local query = Derma_Query("Выберите способ оплаты:", "Оплата", options[1].text, function()
|
||||
net.Start("ixArsenalAction")
|
||||
net.WriteString("buy_armor")
|
||||
net.WriteString(class)
|
||||
net.WriteString(options[1].method)
|
||||
net.SendToServer()
|
||||
frame:Close()
|
||||
end, options[2].text, function()
|
||||
net.Start("ixArsenalAction")
|
||||
net.WriteString("buy_armor")
|
||||
net.WriteString(class)
|
||||
net.WriteString(options[2].method)
|
||||
net.SendToServer()
|
||||
frame:Close()
|
||||
end, "Отмена", function() end)
|
||||
|
||||
if IsValid(query) then
|
||||
query:ShowCloseButton(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
local emptyLabel = vgui.Create("DPanel", armorScroll)
|
||||
emptyLabel:SetSize(armorScroll:GetWide(), 100)
|
||||
emptyLabel:Dock(TOP)
|
||||
emptyLabel:DockMargin(0, 20, 0, 0)
|
||||
emptyLabel.Paint = function(s, w, h)
|
||||
draw.RoundedBox(6, 0, 0, w, h, COLOR_BG_LIGHT)
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
|
||||
draw.SimpleText("⚠ НЕТ ДОСТУПНОЙ ЗАЩИТЫ", "ixSmallFont", w/2, h/2 - 10, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
draw.SimpleText("Обратитесь к командованию", "ixSmallFont", w/2, h/2 + 15, Color(COLOR_TEXT_SECONDARY.r * 0.7, COLOR_TEXT_SECONDARY.g * 0.7, COLOR_TEXT_SECONDARY.b * 0.7), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
end
|
||||
|
||||
PLUGIN.menu = frame
|
||||
end)
|
||||
@@ -0,0 +1,234 @@
|
||||
AddCSLuaFile()
|
||||
|
||||
ENT.Type = "anim"
|
||||
ENT.Base = "base_gmodentity"
|
||||
ENT.PrintName = "Ящик с патронами"
|
||||
ENT.Author = "Server"
|
||||
ENT.Spawnable = true
|
||||
ENT.AdminOnly = true
|
||||
ENT.Category = "[FT] Система арсенала"
|
||||
ENT.bNoPersist = true
|
||||
|
||||
if SERVER then
|
||||
function ENT:Initialize()
|
||||
self:SetModel("models/trenches/prop/para_ammo/ammo_supply.mdl")
|
||||
self:PhysicsInit(SOLID_VPHYSICS)
|
||||
self:SetMoveType(MOVETYPE_VPHYSICS)
|
||||
self:SetSolid(SOLID_VPHYSICS)
|
||||
|
||||
local phys = self:GetPhysicsObject()
|
||||
if IsValid(phys) then
|
||||
phys:Wake()
|
||||
end
|
||||
|
||||
-- Таблица для хранения кулдаунов игроков
|
||||
self.playerCooldowns = {}
|
||||
end
|
||||
|
||||
-- Получить информацию о патронах для оружия
|
||||
function ENT:GetWeaponAmmoInfo(weapon)
|
||||
if not IsValid(weapon) then return nil end
|
||||
|
||||
local ammoType = weapon:GetPrimaryAmmoType()
|
||||
if ammoType == -1 then return nil end
|
||||
|
||||
local clipSize = weapon:GetMaxClip1()
|
||||
if clipSize <= 0 then return nil end
|
||||
|
||||
return {
|
||||
ammoType = ammoType,
|
||||
clipSize = clipSize,
|
||||
ammoName = game.GetAmmoName(ammoType) or "unknown"
|
||||
}
|
||||
end
|
||||
|
||||
-- Использование ящика
|
||||
function ENT:Use(activator, caller)
|
||||
if not IsValid(activator) or not activator:IsPlayer() then return end
|
||||
|
||||
-- Проверка кулдауна (1 секунда)
|
||||
local steamID = activator:SteamID()
|
||||
local now = CurTime()
|
||||
if self.playerCooldowns[steamID] and (now - self.playerCooldowns[steamID]) < 1 then
|
||||
return
|
||||
end
|
||||
|
||||
local char = activator:GetCharacter()
|
||||
if not char then
|
||||
activator:Notify("У вас нет активного персонажа")
|
||||
return
|
||||
end
|
||||
|
||||
local plugin = ix.plugin.list and ix.plugin.list["arsenal"]
|
||||
if not plugin then
|
||||
activator:Notify("Плагин арсенала не найден")
|
||||
return
|
||||
end
|
||||
|
||||
-- Проверка оружия в руках
|
||||
local weapon = activator:GetActiveWeapon()
|
||||
if not IsValid(weapon) then
|
||||
activator:Notify("Возьмите оружие в руки")
|
||||
return
|
||||
end
|
||||
|
||||
-- Получение информации о патронах
|
||||
local ammoInfo = self:GetWeaponAmmoInfo(weapon)
|
||||
local isMedicalKit = weapon:GetClass() == "tacrp_ifak"
|
||||
|
||||
if not ammoInfo and not isMedicalKit then
|
||||
activator:Notify("Это оружие не использует патроны")
|
||||
return
|
||||
end
|
||||
|
||||
-- Специальная логика для медицинской аптечки IFAK
|
||||
if isMedicalKit then
|
||||
-- Проверяем текущие запасы медицинских расходников
|
||||
local bandages = activator:GetAmmoCount(game.GetAmmoID("Bandages"))
|
||||
local quikclots = activator:GetAmmoCount(game.GetAmmoID("Quikclots"))
|
||||
local hemostats = activator:GetAmmoCount(game.GetAmmoID("Hemostats"))
|
||||
|
||||
local maxBandages = 4
|
||||
local maxQuikclots = 3
|
||||
local maxHemostats = 2
|
||||
|
||||
if bandages >= maxBandages and quikclots >= maxQuikclots and hemostats >= maxHemostats then
|
||||
activator:Notify("У вас уже полный комплект медицинских расходников")
|
||||
return
|
||||
end
|
||||
|
||||
-- Рассчитываем стоимость пополнения
|
||||
local costPerItem = 5 -- стоимость за единицу
|
||||
local totalCost = 0
|
||||
local itemsToGive = {}
|
||||
|
||||
if bandages < maxBandages then
|
||||
local needed = maxBandages - bandages
|
||||
totalCost = totalCost + needed * costPerItem
|
||||
itemsToGive["Bandages"] = needed
|
||||
end
|
||||
|
||||
if quikclots < maxQuikclots then
|
||||
local needed = maxQuikclots - quikclots
|
||||
totalCost = totalCost + needed * costPerItem
|
||||
itemsToGive["Quikclots"] = needed
|
||||
end
|
||||
|
||||
if hemostats < maxHemostats then
|
||||
local needed = maxHemostats - hemostats
|
||||
totalCost = totalCost + needed * costPerItem
|
||||
itemsToGive["Hemostats"] = needed
|
||||
end
|
||||
|
||||
-- Проверка достаточно ли очков снабжения
|
||||
if supply < totalCost then
|
||||
activator:Notify(string.format("Недостаточно очков снабжения. Требуется: %d, доступно: %d", totalCost, supply))
|
||||
return
|
||||
end
|
||||
|
||||
-- Выдаем расходники
|
||||
for ammoType, amount in pairs(itemsToGive) do
|
||||
activator:GiveAmmo(amount, ammoType, true)
|
||||
end
|
||||
|
||||
-- Списываем очки снабжения
|
||||
plugin:AddFactionSupply(factionID, -totalCost)
|
||||
|
||||
-- Устанавливаем кулдаун
|
||||
self.playerCooldowns[steamID] = now
|
||||
|
||||
-- Уведомление
|
||||
local itemList = {}
|
||||
for ammoType, amount in pairs(itemsToGive) do
|
||||
table.insert(itemList, string.format("%s: +%d", ammoType, amount))
|
||||
end
|
||||
activator:Notify(string.format("Получены медицинские расходники: %s", table.concat(itemList, ", ")))
|
||||
|
||||
-- Звуковой эффект
|
||||
self:EmitSound("items/ammo_pickup.wav")
|
||||
|
||||
-- Логирование
|
||||
print(string.format("[AMMOBOX] %s получил медицинские расходники. Стоимость: %d очков",
|
||||
activator:Nick(), totalCost))
|
||||
return
|
||||
end
|
||||
|
||||
-- Проверяем конфиг арсенала для этого оружия (одноразовые гранатомёты)
|
||||
local weaponClass = weapon:GetClass()
|
||||
local arsenalConfig = plugin.config and plugin.config.weapons and plugin.config.weapons[weaponClass]
|
||||
-- Детект по конфигу ИЛИ по типу патронов — rpg_round и PanzerFaust3 Rocket всегда одноразовые
|
||||
local launcherAmmoTypes = { ["rpg_round"] = true, ["PanzerFaust3 Rocket"] = true }
|
||||
local isOneShotLauncher = (arsenalConfig and (arsenalConfig.maxAmmo == 1 or arsenalConfig.maxCount == 1))
|
||||
or launcherAmmoTypes[ammoInfo.ammoName]
|
||||
|
||||
-- Проверка фракции
|
||||
local factionID = char:GetFaction()
|
||||
local supply = plugin:GetFactionSupply(factionID)
|
||||
supply = tonumber(supply) or 0
|
||||
|
||||
if isOneShotLauncher then
|
||||
-- Одноразовый гранатомёт: max 1 снаряд в запасе
|
||||
local currentAmmo = activator:GetAmmoCount(ammoInfo.ammoType)
|
||||
if currentAmmo >= 1 then
|
||||
activator:Notify("У вас уже есть снаряд для этого оружия")
|
||||
return
|
||||
end
|
||||
|
||||
-- Стоимость 1 снаряда через costPerBullet
|
||||
local totalCost = 1
|
||||
if supply < totalCost then
|
||||
activator:Notify(string.format("Недостаточно очков снабжения. Требуется: %d, доступно: %d", totalCost, supply))
|
||||
return
|
||||
end
|
||||
|
||||
-- Выдаём ровно 1 снаряд через SetAmmo (GiveAmmo выдаёт минимум 6 для rpg_round)
|
||||
activator:SetAmmo(currentAmmo + 1, ammoInfo.ammoType)
|
||||
plugin:AddFactionSupply(factionID, -totalCost)
|
||||
self.playerCooldowns[steamID] = now
|
||||
|
||||
activator:Notify("Получен 1 снаряд")
|
||||
self:EmitSound("items/ammo_pickup.wav")
|
||||
else
|
||||
-- Обычное оружие: проверка реального количества патронов у игрока
|
||||
local currentAmmo = activator:GetAmmoCount(ammoInfo.ammoType)
|
||||
local maxAllowedAmmo = ammoInfo.clipSize * 6 -- 6 магазинов запаса
|
||||
|
||||
if currentAmmo >= maxAllowedAmmo then
|
||||
local currentMags = math.floor(currentAmmo / ammoInfo.clipSize)
|
||||
activator:Notify(string.format("У вас уже максимум патронов (%d магазинов, 6/6)", currentMags))
|
||||
return
|
||||
end
|
||||
|
||||
-- Рассчитываем количество патронов (1 магазин)
|
||||
local totalAmmo = ammoInfo.clipSize
|
||||
local costPerBullet = 1
|
||||
local totalCost = totalAmmo * costPerBullet
|
||||
|
||||
if supply < totalCost then
|
||||
activator:Notify(string.format("Недостаточно очков снабжения. Требуется: %d, доступно: %d", totalCost, supply))
|
||||
return
|
||||
end
|
||||
|
||||
-- Выдаем патроны
|
||||
activator:GiveAmmo(totalAmmo, ammoInfo.ammoType, true)
|
||||
plugin:AddFactionSupply(factionID, -totalCost)
|
||||
self.playerCooldowns[steamID] = now
|
||||
|
||||
-- Подсчёт текущих магазинов после выдачи
|
||||
local newAmmo = activator:GetAmmoCount(ammoInfo.ammoType)
|
||||
local newMags = math.floor(newAmmo / ammoInfo.clipSize)
|
||||
local magsStr = math.min(newMags, 6) .. "/6"
|
||||
activator:Notify(string.format("Получено %d %s (магазинов: %s)", totalAmmo,
|
||||
totalAmmo == 1 and "патрон" or (totalAmmo < 5 and "патрона" or "патронов"), magsStr))
|
||||
self:EmitSound("items/ammo_pickup.wav")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
function ENT:Draw()
|
||||
self:DrawModel()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
AddCSLuaFile()
|
||||
|
||||
ENT.Type = "anim"
|
||||
ENT.Base = "base_gmodentity"
|
||||
ENT.PrintName = "Арсенал"
|
||||
ENT.Author = "Server"
|
||||
ENT.Spawnable = true
|
||||
ENT.AdminOnly = true
|
||||
ENT.Category = "[FT] Система арсенала"
|
||||
ENT.bNoPersist = true
|
||||
|
||||
if SERVER then
|
||||
function ENT:Initialize()
|
||||
self:SetModel("models/ft/shkaf.mdl")
|
||||
self:PhysicsInit(SOLID_VPHYSICS)
|
||||
self:SetMoveType(MOVETYPE_VPHYSICS)
|
||||
self:SetSolid(SOLID_VPHYSICS)
|
||||
local phys = self:GetPhysicsObject()
|
||||
if IsValid(phys) then phys:Wake() end
|
||||
end
|
||||
|
||||
function ENT:Use(activator, caller)
|
||||
if not IsValid(activator) or not activator:IsPlayer() then return end
|
||||
local char = activator:GetCharacter()
|
||||
if not char then return end
|
||||
|
||||
local plugin = ix.plugin.list and ix.plugin.list["arsenal"]
|
||||
if not plugin then return end
|
||||
|
||||
local avail = plugin:GetAvailableWeapons(char)
|
||||
local weaponsData = {}
|
||||
local weaponHas = {}
|
||||
for class, data in pairs(avail) do
|
||||
weaponsData[class] = data
|
||||
weaponHas[class] = false
|
||||
end
|
||||
|
||||
for _, wep in ipairs(activator:GetWeapons()) do
|
||||
if IsValid(wep) then
|
||||
local c = wep:GetClass()
|
||||
if weaponHas[c] ~= nil then weaponHas[c] = true end
|
||||
end
|
||||
end
|
||||
|
||||
local supply = plugin:GetFactionSupply(char:GetFaction())
|
||||
supply = tonumber(supply) or 0
|
||||
|
||||
local freeRemain = plugin:GetFreeWeaponCooldownForPlayer(activator)
|
||||
local factionID = char:GetFaction()
|
||||
net.Start("ixArsenalOpen")
|
||||
net.WriteUInt(supply, 32)
|
||||
net.WriteUInt(factionID or 0, 8)
|
||||
net.WriteTable(weaponsData)
|
||||
net.WriteTable(weaponHas)
|
||||
net.WriteTable(plugin.config.armor or {})
|
||||
net.WriteUInt(math.max(0, tonumber(freeRemain) or 0), 32)
|
||||
net.Send(activator)
|
||||
end
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
function ENT:Draw()
|
||||
self:DrawModel()
|
||||
--local pos = self:GetPos() + Vector(0,0,10)
|
||||
--local ang = Angle(0, LocalPlayer():EyeAngles().y - 90, 90)
|
||||
--cam.Start3D2D(pos, ang, 0.1)
|
||||
-- draw.SimpleText("АРСЕНАЛ", "DermaDefaultBold", 0, 0, Color(200,200,200), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
--cam.End3D2D()
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,596 @@
|
||||
AddCSLuaFile()
|
||||
|
||||
ENT.Type = "anim"
|
||||
ENT.Base = "base_gmodentity"
|
||||
ENT.PrintName = "Гардероб"
|
||||
ENT.Author = "Server"
|
||||
ENT.Spawnable = true
|
||||
ENT.AdminOnly = true
|
||||
ENT.Category = "[FT] Система арсенала"
|
||||
ENT.bNoPersist = true
|
||||
|
||||
if SERVER then
|
||||
function ENT:Initialize()
|
||||
self:SetModel("models/props_wasteland/controlroom_storagecloset001a.mdl")
|
||||
self:PhysicsInit(SOLID_VPHYSICS)
|
||||
self:SetMoveType(MOVETYPE_VPHYSICS)
|
||||
self:SetSolid(SOLID_VPHYSICS)
|
||||
local phys = self:GetPhysicsObject()
|
||||
if IsValid(phys) then phys:Wake() end
|
||||
self.playerCooldowns = {}
|
||||
end
|
||||
|
||||
function ENT:GetModelBodygroups(model)
|
||||
local tempEnt = ents.Create("prop_dynamic")
|
||||
if not IsValid(tempEnt) then return {} end
|
||||
tempEnt:SetModel(model)
|
||||
tempEnt:Spawn()
|
||||
local bodygroups = {}
|
||||
for i = 0, tempEnt:GetNumBodyGroups() - 1 do
|
||||
local name = tempEnt:GetBodygroupName(i)
|
||||
local count = tempEnt:GetBodygroupCount(i)
|
||||
if count > 1 then
|
||||
bodygroups[i] = {name = name, count = count, index = i}
|
||||
end
|
||||
end
|
||||
tempEnt:Remove()
|
||||
return bodygroups
|
||||
end
|
||||
|
||||
function ENT:GetModelSkinCount(model)
|
||||
local tempEnt = ents.Create("prop_dynamic")
|
||||
if not IsValid(tempEnt) then return 0 end
|
||||
tempEnt:SetModel(model)
|
||||
tempEnt:Spawn()
|
||||
local skinCount = tempEnt:SkinCount() or 0
|
||||
tempEnt:Remove()
|
||||
return skinCount
|
||||
end
|
||||
|
||||
function ENT:IsBlacklisted(model, type, index)
|
||||
local plugin = ix.plugin.list and ix.plugin.list["arsenal"]
|
||||
if not plugin or not plugin.config.wardrobeBlacklist then return false end
|
||||
local blacklist = plugin.config.wardrobeBlacklist[model]
|
||||
if not blacklist then return false end
|
||||
if type == "bodygroup" and blacklist.bodygroups then
|
||||
return table.HasValue(blacklist.bodygroups, index)
|
||||
elseif type == "skin" and blacklist.skins then
|
||||
return table.HasValue(blacklist.skins, index)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function ENT:Use(activator)
|
||||
if not IsValid(activator) or not activator:IsPlayer() then return end
|
||||
if not self.playerCooldowns then self.playerCooldowns = {} end
|
||||
local steamID = activator:SteamID()
|
||||
local now = CurTime()
|
||||
if self.playerCooldowns[steamID] and (now - self.playerCooldowns[steamID]) < 2 then return end
|
||||
local char = activator:GetCharacter()
|
||||
if not char then return end
|
||||
local model = activator:GetModel()
|
||||
if not model then return end
|
||||
|
||||
local bodygroups = self:GetModelBodygroups(model)
|
||||
local skinCount = self:GetModelSkinCount(model)
|
||||
local availableBodygroups = {}
|
||||
for idx, data in pairs(bodygroups) do
|
||||
if not self:IsBlacklisted(model, "bodygroup", idx) then
|
||||
availableBodygroups[idx] = data
|
||||
end
|
||||
end
|
||||
local availableSkins = {}
|
||||
for i = 0, skinCount - 1 do
|
||||
if not self:IsBlacklisted(model, "skin", i) then
|
||||
table.insert(availableSkins, i)
|
||||
end
|
||||
end
|
||||
|
||||
local currentBodygroups = {}
|
||||
for idx in pairs(availableBodygroups) do
|
||||
currentBodygroups[idx] = activator:GetBodygroup(idx)
|
||||
end
|
||||
local currentSkin = activator:GetSkin()
|
||||
local currentPatch = char:GetData("patchIndex", 1)
|
||||
|
||||
self.playerCooldowns[steamID] = now
|
||||
net.Start("ixWardrobeOpen")
|
||||
net.WriteString(model)
|
||||
net.WriteTable(availableBodygroups)
|
||||
net.WriteTable(availableSkins)
|
||||
net.WriteTable(currentBodygroups)
|
||||
net.WriteUInt(currentSkin, 8)
|
||||
net.WriteUInt(currentPatch, 8)
|
||||
net.Send(activator)
|
||||
end
|
||||
|
||||
net.Receive("ixWardrobeApply", function(len, client)
|
||||
if not IsValid(client) then return end
|
||||
local char = client:GetCharacter()
|
||||
if not char then return end
|
||||
|
||||
local isWipe = net.ReadBool()
|
||||
local plugin = ix.plugin.list and ix.plugin.list["arsenal"]
|
||||
|
||||
if isWipe then
|
||||
client:SetSkin(0)
|
||||
char:SetData("skin", 0)
|
||||
char:SetData("patchIndex", 1)
|
||||
char:SetData("bodygroups", nil)
|
||||
|
||||
local bgs = client:GetBodyGroups()
|
||||
for _, bg in ipairs(bgs) do
|
||||
client:SetBodygroup(bg.id, 0)
|
||||
end
|
||||
|
||||
if plugin and plugin.config.patchIndex then
|
||||
client:SetSubMaterial(plugin.config.patchIndex, "")
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local bodygroups = net.ReadTable()
|
||||
local skin = net.ReadUInt(8)
|
||||
local patch = net.ReadUInt(8)
|
||||
local model = client:GetModel()
|
||||
|
||||
local faction = char:GetFaction()
|
||||
for idx, value in pairs(bodygroups) do
|
||||
if plugin and plugin.config.wardrobeAccess and plugin.config.wardrobeAccess[model] then
|
||||
local bAccess = plugin.config.wardrobeAccess[model][idx]
|
||||
if bAccess and bAccess[value] then
|
||||
if not table.HasValue(bAccess[value], faction) then
|
||||
value = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
client:SetBodygroup(idx, value)
|
||||
end
|
||||
|
||||
client:SetSkin(skin)
|
||||
char:SetData("bodygroups", bodygroups)
|
||||
char:SetData("skin", skin)
|
||||
char:SetData("patchIndex", patch)
|
||||
|
||||
if plugin and plugin.config.patchIndex and plugin.config.patchMaterials then
|
||||
local pMat = plugin.config.patchMaterials[patch]
|
||||
if pMat then
|
||||
client:SetSubMaterial(plugin.config.patchIndex, pMat)
|
||||
else
|
||||
client:SetSubMaterial(plugin.config.patchIndex, "")
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
local C_BG_D = Color(3, 5, 4)
|
||||
local C_BG_M = Color(8, 12, 10)
|
||||
local C_BG_L = Color(12, 18, 14)
|
||||
local C_PRI = Color(27, 94, 32)
|
||||
local C_PRI_H = Color(33, 110, 38)
|
||||
local C_ACC = Color(56, 102, 35)
|
||||
local C_ACC_H = Color(66, 120, 45)
|
||||
local C_BDR = Color(46, 125, 50, 80)
|
||||
local C_TXT_P = Color(165, 214, 167)
|
||||
local C_TXT_S = Color(129, 199, 132)
|
||||
local C_GRAD = Color(27, 94, 32, 15)
|
||||
local C_WARN = Color(150, 40, 40)
|
||||
local C_WARN_H = Color(180, 50, 50)
|
||||
|
||||
function ENT:Draw()
|
||||
self:DrawModel()
|
||||
end
|
||||
|
||||
net.Receive("ixWardrobeOpen", function()
|
||||
local model = net.ReadString()
|
||||
local bodygroups = net.ReadTable()
|
||||
local availableSkins = net.ReadTable()
|
||||
local currentBodygroups = net.ReadTable()
|
||||
local currentSkin = net.ReadUInt(8)
|
||||
local currentPatch = net.ReadUInt(8)
|
||||
if IsRecruit(ply) then
|
||||
ply:Notify("Новоприбывший не имеет доступа к гардеробу.")
|
||||
return
|
||||
end
|
||||
|
||||
local plugin = ix.plugin.list and ix.plugin.list["arsenal"]
|
||||
if not plugin then return end
|
||||
|
||||
local frame = vgui.Create("DFrame")
|
||||
frame:SetSize(900, 600)
|
||||
frame:Center()
|
||||
frame:SetTitle("")
|
||||
frame:ShowCloseButton(false)
|
||||
frame:MakePopup()
|
||||
frame:SetBackgroundBlur(true)
|
||||
frame.Paint = function(s, w, h)
|
||||
draw.RoundedBox(8, 0, 0, w, h, C_BG_D)
|
||||
surface.SetDrawColor(C_GRAD)
|
||||
surface.DrawRect(0, 0, w, 3)
|
||||
draw.RoundedBoxEx(8, 0, 0, w, 50, C_BG_M, true, true, false, false)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawLine(0, 50, w, 50)
|
||||
draw.SimpleText("ГАРДЕРОБ", "DermaLarge", w/2, 25, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
surface.SetDrawColor(C_ACC)
|
||||
surface.DrawRect(20, 48, 30, 2)
|
||||
surface.DrawRect(w-50, 48, 30, 2)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
end
|
||||
|
||||
local closeBtn = vgui.Create("DButton", frame)
|
||||
closeBtn:SetPos(frame:GetWide() - 40, 10)
|
||||
closeBtn:SetSize(30, 30)
|
||||
closeBtn:SetText("")
|
||||
closeBtn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and C_ACC_H or C_ACC
|
||||
draw.RoundedBox(4, 0, 0, w, h, col)
|
||||
draw.SimpleText("X", "DermaLarge", w/2, h/2-2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
closeBtn.DoClick = function() frame:Close() end
|
||||
|
||||
local modelPanel = vgui.Create("DModelPanel", frame)
|
||||
modelPanel:SetPos(20, 60)
|
||||
modelPanel:SetSize(400, 460)
|
||||
modelPanel:SetModel(model)
|
||||
|
||||
local oldPaint = modelPanel.Paint
|
||||
modelPanel.Paint = function(s, w, h)
|
||||
draw.RoundedBox(6, 0, 0, w, h, C_BG_M)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
oldPaint(s, w, h)
|
||||
end
|
||||
|
||||
modelPanel.LayoutEntity = function(s, ent)
|
||||
if not s.bAnimSet then
|
||||
local seq = ent:LookupSequence("idle_subtle")
|
||||
if seq <= 0 then seq = ent:LookupSequence("idle_all_01") end
|
||||
if seq > 0 then
|
||||
ent:SetSequence(seq)
|
||||
end
|
||||
s.bAnimSet = true
|
||||
end
|
||||
|
||||
if (s.bDragging) then
|
||||
local x, y = gui.MousePos()
|
||||
s.camAnims.rot.yaw = s.camAnims.rot.yaw + (x - s.lastX) * 0.8
|
||||
s.camAnims.rot.pitch = math.Clamp(s.camAnims.rot.pitch + (y - s.lastY) * 0.8, -45, 45)
|
||||
s.lastX, s.lastY = x, y
|
||||
end
|
||||
|
||||
local speed = FrameTime() * 10
|
||||
s.curZoom = Lerp(speed, s.curZoom, s.camAnims.zoom)
|
||||
s.curRot = LerpAngle(speed, s.curRot, s.camAnims.rot)
|
||||
|
||||
local eyePos = ent:GetPos() + Vector(0, 0, 40)
|
||||
s:SetCamPos(eyePos - s.curRot:Forward() * s.curZoom)
|
||||
s:SetLookAng(s.curRot)
|
||||
|
||||
ent:FrameAdvance((RealTime() - (s.lastTick or RealTime())) * 1)
|
||||
s.lastTick = RealTime()
|
||||
end
|
||||
|
||||
modelPanel.camAnims = { zoom = 60, rot = Angle(0, 180, 0) }
|
||||
modelPanel.curZoom = 60
|
||||
modelPanel.curRot = Angle(0, 180, 0)
|
||||
|
||||
local function SetCameraAngle(angle)
|
||||
if angle == "front" then
|
||||
modelPanel.camAnims.rot = Angle(0, 180, 0)
|
||||
elseif angle == "side" then
|
||||
modelPanel.camAnims.rot = Angle(0, 90, 0)
|
||||
elseif angle == "back" then
|
||||
modelPanel.camAnims.rot = Angle(0, 0, 0)
|
||||
end
|
||||
end
|
||||
|
||||
modelPanel.OnMousePressed = function(s, code)
|
||||
if code == MOUSE_LEFT then
|
||||
s.bDragging = true
|
||||
s.lastX, s.lastY = gui.MousePos()
|
||||
end
|
||||
end
|
||||
|
||||
modelPanel.OnMouseReleased = function(s, code)
|
||||
if code == MOUSE_LEFT then
|
||||
s.bDragging = false
|
||||
end
|
||||
end
|
||||
|
||||
modelPanel.OnCursorExited = function(s)
|
||||
s.bDragging = false
|
||||
end
|
||||
|
||||
modelPanel.OnMouseWheeled = function(s, delta)
|
||||
s.camAnims.zoom = math.Clamp(s.camAnims.zoom - delta * 15, 20, 120)
|
||||
end
|
||||
|
||||
timer.Simple(0.05, function()
|
||||
if IsValid(modelPanel) then SetCameraAngle("front") end
|
||||
end)
|
||||
|
||||
modelPanel.Think = function(s)
|
||||
local entity = s:GetEntity()
|
||||
if IsValid(entity) then
|
||||
for idx, value in pairs(currentBodygroups) do
|
||||
if entity:GetBodygroup(idx) ~= value then
|
||||
entity:SetBodygroup(idx, value)
|
||||
end
|
||||
end
|
||||
if entity:GetSkin() ~= currentSkin then
|
||||
entity:SetSkin(currentSkin)
|
||||
end
|
||||
|
||||
if plugin and plugin.config.patchIndex and plugin.config.patchMaterials then
|
||||
local pMat = plugin.config.patchMaterials[currentPatch]
|
||||
if pMat then
|
||||
entity:SetSubMaterial(plugin.config.patchIndex, pMat)
|
||||
else
|
||||
entity:SetSubMaterial(plugin.config.patchIndex, "")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local viewButtons = vgui.Create("DPanel", frame)
|
||||
viewButtons:SetPos(20, 530)
|
||||
viewButtons:SetSize(400, 50)
|
||||
viewButtons.Paint = function(s, w, h)
|
||||
draw.RoundedBox(6, 0, 0, w, h, C_BG_M)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
end
|
||||
|
||||
local bw = 125
|
||||
local bs = 12
|
||||
|
||||
local frontBtn = vgui.Create("DButton", viewButtons)
|
||||
frontBtn:SetPos(10, 10)
|
||||
frontBtn:SetSize(bw, 30)
|
||||
frontBtn:SetText("")
|
||||
frontBtn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and C_PRI_H or C_PRI
|
||||
draw.RoundedBox(4, 0, 0, w, h, col)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
draw.SimpleText("СПЕРЕДИ", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
frontBtn.DoClick = function() SetCameraAngle("front") end
|
||||
|
||||
local sideBtn = vgui.Create("DButton", viewButtons)
|
||||
sideBtn:SetPos(10 + bw + bs, 10)
|
||||
sideBtn:SetSize(bw, 30)
|
||||
sideBtn:SetText("")
|
||||
sideBtn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and C_PRI_H or C_PRI
|
||||
draw.RoundedBox(4, 0, 0, w, h, col)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
draw.SimpleText("СБОКУ", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
sideBtn.DoClick = function() SetCameraAngle("side") end
|
||||
|
||||
local backBtn = vgui.Create("DButton", viewButtons)
|
||||
backBtn:SetPos(10 + (bw + bs) * 2, 10)
|
||||
backBtn:SetSize(bw, 30)
|
||||
backBtn:SetText("")
|
||||
backBtn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and C_PRI_H or C_PRI
|
||||
draw.RoundedBox(4, 0, 0, w, h, col)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
draw.SimpleText("СЗАДИ", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
backBtn.DoClick = function() SetCameraAngle("back") end
|
||||
|
||||
local settingsPanel = vgui.Create("DPanel", frame)
|
||||
settingsPanel:SetPos(440, 60)
|
||||
settingsPanel:SetSize(440, 520)
|
||||
settingsPanel.Paint = function(s, w, h)
|
||||
draw.RoundedBox(6, 0, 0, w, h, C_BG_M)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
end
|
||||
|
||||
local settingsScroll = vgui.Create("DScrollPanel", settingsPanel)
|
||||
settingsScroll:Dock(FILL)
|
||||
settingsScroll:DockMargin(10, 10, 10, 140)
|
||||
|
||||
local sbar = settingsScroll:GetVBar()
|
||||
sbar:SetWide(8)
|
||||
sbar.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, C_BG_D) end
|
||||
sbar.btnGrip.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, C_ACC) end
|
||||
sbar.btnUp.Paint = function() end
|
||||
sbar.btnDown.Paint = function() end
|
||||
|
||||
local yPos = 5
|
||||
|
||||
if plugin and plugin.config.patchMaterials and #plugin.config.patchMaterials > 0 then
|
||||
local patchLabel = vgui.Create("DPanel", settingsScroll)
|
||||
patchLabel:SetPos(10, yPos)
|
||||
patchLabel:SetSize(400, 35)
|
||||
patchLabel.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, C_BG_L)
|
||||
surface.SetDrawColor(C_ACC)
|
||||
surface.DrawRect(0, 0, 3, h)
|
||||
draw.SimpleText("ПАТЧИ", "DermaLarge", 10, h/2, C_TXT_P, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
yPos = yPos + 40
|
||||
|
||||
local pH = vgui.Create("DPanel", settingsScroll)
|
||||
pH:SetPos(10, yPos)
|
||||
pH:SetSize(400, 65)
|
||||
pH.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and C_BG_L or C_BG_D
|
||||
draw.RoundedBox(6, 0, 0, w, h, col)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
end
|
||||
|
||||
local pS = vgui.Create("DNumSlider", pH)
|
||||
pS:SetPos(5, 15)
|
||||
pS:SetSize(390, 40)
|
||||
pS:SetMin(1)
|
||||
pS:SetMax(#plugin.config.patchMaterials)
|
||||
pS:SetDecimals(0)
|
||||
pS:SetValue(currentPatch)
|
||||
pS:SetText("Тип патча")
|
||||
pS.Slider.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 0, h/2-2, w, 4, C_BG_D)
|
||||
local maxV = math.max(1, pS:GetMax() - pS:GetMin())
|
||||
local frac = (pS:GetValue() - pS:GetMin()) / maxV
|
||||
draw.RoundedBox(4, 0, h/2-2, w * frac, 4, C_ACC)
|
||||
end
|
||||
pS.Slider.Knob.Paint = function(s, w, h)
|
||||
draw.RoundedBox(w/2, 0, 0, w, h, C_PRI)
|
||||
if s:IsHovered() then draw.RoundedBox(w/2, 2, 2, w-4, h-4, C_ACC_H) end
|
||||
end
|
||||
pS.OnValueChanged = function(s, value) currentPatch = math.floor(value) end
|
||||
|
||||
yPos = yPos + 70
|
||||
end
|
||||
|
||||
if table.Count(bodygroups) > 0 then
|
||||
local bgLabel = vgui.Create("DPanel", settingsScroll)
|
||||
bgLabel:SetPos(10, yPos)
|
||||
bgLabel:SetSize(400, 35)
|
||||
bgLabel.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, C_BG_L)
|
||||
surface.SetDrawColor(C_ACC)
|
||||
surface.DrawRect(0, 0, 3, h)
|
||||
draw.SimpleText("ЧАСТИ ТЕЛА", "DermaLarge", 10, h/2, C_TXT_P, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
yPos = yPos + 40
|
||||
|
||||
for idx, data in SortedPairsByMemberValue(bodygroups, "index") do
|
||||
local bgPanel = vgui.Create("DPanel", settingsScroll)
|
||||
bgPanel:SetPos(10, yPos)
|
||||
bgPanel:SetSize(400, 65)
|
||||
bgPanel.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and C_BG_L or C_BG_D
|
||||
draw.RoundedBox(6, 0, 0, w, h, col)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
end
|
||||
|
||||
local label = vgui.Create("DLabel", bgPanel)
|
||||
label:SetPos(10, 8)
|
||||
label:SetSize(380, 20)
|
||||
label:SetFont("DermaDefault")
|
||||
label:SetText(data.name)
|
||||
label:SetTextColor(C_TXT_P)
|
||||
|
||||
local slider = vgui.Create("DNumSlider", bgPanel)
|
||||
slider:SetPos(5, 28)
|
||||
slider:SetSize(390, 30)
|
||||
slider:SetMin(0)
|
||||
slider:SetMax(data.count - 1)
|
||||
slider:SetDecimals(0)
|
||||
slider:SetValue(currentBodygroups[idx] or 0)
|
||||
slider:SetText("")
|
||||
slider.Slider.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 0, h/2-2, w, 4, C_BG_D)
|
||||
local maxV = math.max(1, slider:GetMax() - slider:GetMin())
|
||||
local frac = (slider:GetValue() - slider:GetMin()) / maxV
|
||||
draw.RoundedBox(4, 0, h/2-2, w * frac, 4, C_ACC)
|
||||
end
|
||||
slider.Slider.Knob.Paint = function(s, w, h)
|
||||
draw.RoundedBox(w/2, 0, 0, w, h, C_PRI)
|
||||
if s:IsHovered() then draw.RoundedBox(w/2, 2, 2, w-4, h-4, C_ACC_H) end
|
||||
end
|
||||
slider.OnValueChanged = function(s, value) currentBodygroups[idx] = math.floor(value) end
|
||||
|
||||
yPos = yPos + 70
|
||||
end
|
||||
end
|
||||
|
||||
if #availableSkins > 1 then
|
||||
local skinLabel = vgui.Create("DPanel", settingsScroll)
|
||||
skinLabel:SetPos(10, yPos)
|
||||
skinLabel:SetSize(400, 35)
|
||||
skinLabel.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, C_BG_L)
|
||||
surface.SetDrawColor(C_ACC)
|
||||
surface.DrawRect(0, 0, 3, h)
|
||||
draw.SimpleText("СКИНЫ", "DermaLarge", 10, h/2, C_TXT_P, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
yPos = yPos + 40
|
||||
|
||||
local skinPanel = vgui.Create("DPanel", settingsScroll)
|
||||
skinPanel:SetPos(10, yPos)
|
||||
skinPanel:SetSize(400, 65)
|
||||
skinPanel.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and C_BG_L or C_BG_D
|
||||
draw.RoundedBox(6, 0, 0, w, h, col)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
end
|
||||
|
||||
local skinSlider = vgui.Create("DNumSlider", skinPanel)
|
||||
skinSlider:SetPos(5, 15)
|
||||
skinSlider:SetSize(390, 40)
|
||||
skinSlider:SetMin(0)
|
||||
skinSlider:SetMax(#availableSkins)
|
||||
skinSlider:SetDecimals(0)
|
||||
skinSlider:SetValue(currentSkin)
|
||||
skinSlider:SetText("Скин")
|
||||
skinSlider.Slider.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 0, h/2-2, w, 4, C_BG_D)
|
||||
local maxV = math.max(1, skinSlider:GetMax() - skinSlider:GetMin())
|
||||
local frac = (skinSlider:GetValue() - skinSlider:GetMin()) / maxV
|
||||
draw.RoundedBox(4, 0, h/2-2, w * frac, 4, C_ACC)
|
||||
end
|
||||
skinSlider.Slider.Knob.Paint = function(s, w, h)
|
||||
draw.RoundedBox(w/2, 0, 0, w, h, C_PRI)
|
||||
if s:IsHovered() then draw.RoundedBox(w/2, 2, 2, w-4, h-4, C_ACC_H) end
|
||||
end
|
||||
skinSlider.OnValueChanged = function(s, value) currentSkin = math.floor(value) end
|
||||
|
||||
yPos = yPos + 70
|
||||
end
|
||||
|
||||
local wipeBtn = vgui.Create("DButton", settingsPanel)
|
||||
wipeBtn:SetPos(10, 390)
|
||||
wipeBtn:SetSize(420, 50)
|
||||
wipeBtn:SetText("")
|
||||
wipeBtn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and C_WARN_H or C_WARN
|
||||
draw.RoundedBox(6, 0, 0, w, h, col)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
draw.SimpleText("СБРОСИТЬ", "DermaLarge", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
wipeBtn.DoClick = function()
|
||||
net.Start("ixWardrobeApply")
|
||||
net.WriteBool(true)
|
||||
net.SendToServer()
|
||||
frame:Close()
|
||||
end
|
||||
|
||||
local applyBtn = vgui.Create("DButton", settingsPanel)
|
||||
applyBtn:SetPos(10, 450)
|
||||
applyBtn:SetSize(420, 60)
|
||||
applyBtn:SetText("")
|
||||
applyBtn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and C_PRI_H or C_PRI
|
||||
draw.RoundedBox(6, 0, 0, w, h, col)
|
||||
surface.SetDrawColor(C_GRAD)
|
||||
surface.DrawRect(0, 0, w, h/2)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
draw.SimpleText("ПРИМЕНИТЬ", "DermaLarge", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
applyBtn.DoClick = function()
|
||||
net.Start("ixWardrobeApply")
|
||||
net.WriteBool(false)
|
||||
net.WriteTable(currentBodygroups)
|
||||
net.WriteUInt(currentSkin, 8)
|
||||
net.WriteUInt(currentPatch, 8)
|
||||
net.SendToServer()
|
||||
frame:Close()
|
||||
end
|
||||
end)
|
||||
end
|
||||
979
garrysmod/gamemodes/militaryrp/plugins/arsenal/sh_config.lua
Normal file
979
garrysmod/gamemodes/militaryrp/plugins/arsenal/sh_config.lua
Normal file
@@ -0,0 +1,979 @@
|
||||
-- проверка пениса
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
PLUGIN.config = {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
currencyName = "Очки снабжения",
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- Начальное снабжение для фракций
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
startSupply = {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[FACTION_RUSSIAN or 1] = 2000,
|
||||
[FACTION_UKRAINE or 2] = 2000,
|
||||
}, -- [FACTION_ID] = amount
|
||||
|
||||
-- Максиммальное снабжение
|
||||
maxSupply = 20000,
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- Минимум, при котором арсенал считается закрытым
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
minSupply = 0,
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- Cooldown for free weapons (supplyPrice == 0), per-player, seconds
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
freeWeaponCooldown = 3600, -- default 1 hour
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- Currency symbols per faction (client uses this to display money symbol)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
currencySymbol = {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[FACTION_RUSSIAN or 1] = "₽",
|
||||
[FACTION_UKRAINE or 2] = "$",
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
weapons = {
|
||||
["tfa_inss_wpn_ak12"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 11000,
|
||||
["supplyPrice"] = 220,
|
||||
["name"] = "AK-12"
|
||||
},
|
||||
["tacrp_mr96"] = {
|
||||
["category"] = "secondary",
|
||||
["moneyPrice"] = 200,
|
||||
["supplyPrice"] = 1,
|
||||
["name"] = "MR-96"
|
||||
},
|
||||
["tacrp_ak_ak74u"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 3000,
|
||||
["supplyPrice"] = 50,
|
||||
["name"] = "AKС-74У"
|
||||
},
|
||||
["tacrp_io_glock18"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 100,
|
||||
["name"] = "Glock 18C"
|
||||
},
|
||||
["tacrp_ks23"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 100,
|
||||
["name"] = "КС-23"
|
||||
},
|
||||
["tacrp_as50"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 20000,
|
||||
["supplyPrice"] = 1000,
|
||||
["name"] = "AS50"
|
||||
},
|
||||
["weapon_lvsrepair"] = {
|
||||
["category"] = "tool1",
|
||||
["moneyPrice"] = 100,
|
||||
["supplyPrice"] = 10,
|
||||
["name"] = "Сварка"
|
||||
},
|
||||
["tacrp_io_trg42"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 15000,
|
||||
["supplyPrice"] = 450,
|
||||
["name"] = "Sako TRG-42"
|
||||
},
|
||||
["tacrp_sd_dual_degala"] = {
|
||||
["category"] = "secondary",
|
||||
["moneyPrice"] = 200,
|
||||
["supplyPrice"] = 1,
|
||||
["name"] = "Двойные Deagle"
|
||||
},
|
||||
["weapon_cigarette_camel"] = {
|
||||
["category"] = "tool1",
|
||||
["moneyPrice"] = 100,
|
||||
["supplyPrice"] = 10,
|
||||
["name"] = "Сигаретка"
|
||||
},
|
||||
["tacrp_io_sg550"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 12000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "SIG SG 550"
|
||||
},
|
||||
["tacrp_io_rpk"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 15000,
|
||||
["supplyPrice"] = 150,
|
||||
["name"] = "РПК-74"
|
||||
},
|
||||
["tacrp_uzi"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 100,
|
||||
["name"] = "UZI"
|
||||
},
|
||||
["parachute_swep"] = {
|
||||
["category"] = "tool4",
|
||||
["moneyPrice"] = 150,
|
||||
["supplyPrice"] = 10,
|
||||
["name"] = "Парашют"
|
||||
},
|
||||
["tacrp_hk417"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "HK 417"
|
||||
},
|
||||
["tacrp_sd_bizon"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 100,
|
||||
["name"] = "ПП-19 Бизон"
|
||||
},
|
||||
["weapon_sw_at4"] = {
|
||||
["moneyPrice"] = 4500,
|
||||
["supplyPrice"] = 450,
|
||||
["maxCount"] = 1,
|
||||
["ammoType"] = "rpg_round",
|
||||
["category"] = "heavy",
|
||||
["maxAmmo"] = 1,
|
||||
["name"] = "AT4"
|
||||
},
|
||||
["fas2_ifak"] = {
|
||||
["category"] = "tool7",
|
||||
["moneyPrice"] = 500,
|
||||
["supplyPrice"] = 50,
|
||||
["name"] = "Аптечка"
|
||||
},
|
||||
["bandage"] = {
|
||||
["category"] = "tool7",
|
||||
["moneyPrice"] = 500,
|
||||
["supplyPrice"] = 100,
|
||||
["name"] = "Бинт"
|
||||
},
|
||||
["tacrp_io_saiga"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "Сайга-12К"
|
||||
},
|
||||
["tacrp_nade_frag"] = {
|
||||
["category"] = "grenade",
|
||||
["moneyPrice"] = 300,
|
||||
["supplyPrice"] = 30,
|
||||
["name"] = "Осколочная граната"
|
||||
},
|
||||
["tacrp_g36k"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "G36K"
|
||||
},
|
||||
["tfa_inss_wpn_l1a1"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 12000,
|
||||
["supplyPrice"] = 240,
|
||||
["name"] = "L1A1"
|
||||
},
|
||||
["bodycam_tablet"] = {
|
||||
["category"] = "tool3",
|
||||
["moneyPrice"] = 500,
|
||||
["supplyPrice"] = 50,
|
||||
["name"] = "Боди-камера"
|
||||
},
|
||||
["gmod_camera"] = {
|
||||
["category"] = "tool3",
|
||||
["moneyPrice"] = 50,
|
||||
["supplyPrice"] = 10,
|
||||
["name"] = "Камера"
|
||||
},
|
||||
["admin_defib"] = {
|
||||
["category"] = "tool7",
|
||||
["moneyPrice"] = 8000,
|
||||
["supplyPrice"] = 5000,
|
||||
["name"] = "Дефибриллятор"
|
||||
},
|
||||
["tacrp_sg551"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 20000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "SG 551"
|
||||
},
|
||||
["tacrp_knife"] = {
|
||||
["category"] = "melee",
|
||||
["moneyPrice"] = 50,
|
||||
["supplyPrice"] = 0,
|
||||
["name"] = "Нож"
|
||||
},
|
||||
["guitar"] = {
|
||||
["category"] = "tool3",
|
||||
["moneyPrice"] = 200,
|
||||
["supplyPrice"] = 10,
|
||||
["name"] = "Гитара"
|
||||
},
|
||||
["v92_bf2_medikit"] = {
|
||||
["category"] = "tool9",
|
||||
["moneyPrice"] = 1500,
|
||||
["supplyPrice"] = 150,
|
||||
["name"] = "Аптечка"
|
||||
},
|
||||
["weapon_sw_9k38"] = {
|
||||
["category"] = "heavy",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 500,
|
||||
["name"] = "9K38"
|
||||
},
|
||||
["tacrp_mp7"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 100,
|
||||
["name"] = "MP7"
|
||||
},
|
||||
["weapon_lvsspikestrip"] = {
|
||||
["category"] = "tool2",
|
||||
["moneyPrice"] = 100,
|
||||
["supplyPrice"] = 10,
|
||||
["name"] = "Шипы"
|
||||
},
|
||||
["tacrp_p250"] = {
|
||||
["category"] = "secondary",
|
||||
["moneyPrice"] = 200,
|
||||
["supplyPrice"] = 1,
|
||||
["name"] = "P250"
|
||||
},
|
||||
["tacrp_m4"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "HK416"
|
||||
},
|
||||
["tfa_inss_wpn_m16a4"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 12000,
|
||||
["supplyPrice"] = 240,
|
||||
["name"] = "M16A4"
|
||||
},
|
||||
["tacrp_ak_svd"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 15000,
|
||||
["supplyPrice"] = 450,
|
||||
["name"] = "СВД"
|
||||
},
|
||||
["tacrp_io_xm8lmg"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 12000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "ХМ8 LMG"
|
||||
},
|
||||
["tacrp_mg4"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 15000,
|
||||
["supplyPrice"] = 300,
|
||||
["name"] = "HK MG4"
|
||||
},
|
||||
["tacrp_io_degala"] = {
|
||||
["category"] = "secondary",
|
||||
["moneyPrice"] = 200,
|
||||
["supplyPrice"] = 1,
|
||||
["name"] = "Desert Eagle"
|
||||
},
|
||||
["tacrp_mp5"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 100,
|
||||
["name"] = "MP5A3"
|
||||
},
|
||||
["special_bandage"] = {
|
||||
["category"] = "tool8",
|
||||
["moneyPrice"] = 1000,
|
||||
["supplyPrice"] = 500,
|
||||
["name"] = "Шина от переломов"
|
||||
},
|
||||
["tacrp_ak_aek971"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "АЕК-971"
|
||||
},
|
||||
["tacrp_ak_ak12"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 11000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "АК-12"
|
||||
},
|
||||
["weapon_sw_rpg26"] = {
|
||||
["moneyPrice"] = 4500,
|
||||
["supplyPrice"] = 450,
|
||||
["maxCount"] = 1,
|
||||
["ammoType"] = "rpg_round",
|
||||
["category"] = "heavy",
|
||||
["maxAmmo"] = 1,
|
||||
["name"] = "РПГ-26"
|
||||
},
|
||||
["tacrp_spr"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "M700"
|
||||
},
|
||||
["tacrp_ex_m4a1"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 15000,
|
||||
["supplyPrice"] = 300,
|
||||
["name"] = "М4А1"
|
||||
},
|
||||
["tfa_new_inss_mk18"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 12000,
|
||||
["supplyPrice"] = 240,
|
||||
["name"] = "MK18"
|
||||
},
|
||||
["tacrp_io_fiveseven"] = {
|
||||
["category"] = "secondary",
|
||||
["moneyPrice"] = 200,
|
||||
["supplyPrice"] = 1,
|
||||
["name"] = "FiveSeven"
|
||||
},
|
||||
["engineertoolmines"] = {
|
||||
["category"] = "tool5",
|
||||
["moneyPrice"] = 50000,
|
||||
["supplyPrice"] = 1000,
|
||||
["name"] = "Мины"
|
||||
},
|
||||
["tacrp_pdw"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 3000,
|
||||
["supplyPrice"] = 100,
|
||||
["name"] = "KAC PDW"
|
||||
},
|
||||
["tacrp_ak_an94"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "АН-94"
|
||||
},
|
||||
["weapon_r_handcuffs"] = {
|
||||
["category"] = "tool8",
|
||||
["moneyPrice"] = 1000,
|
||||
["supplyPrice"] = 10,
|
||||
["name"] = "Наручники"
|
||||
},
|
||||
["tfa_inss_wpn_fn_fal"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 12000,
|
||||
["supplyPrice"] = 240,
|
||||
["name"] = "FN FAL"
|
||||
},
|
||||
["weapon_rope_knife"] = {
|
||||
["category"] = "tool6",
|
||||
["moneyPrice"] = 50000,
|
||||
["supplyPrice"] = 500,
|
||||
["name"] = "Крюк кошка"
|
||||
},
|
||||
["tacrp_io_sg550r"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 15000,
|
||||
["supplyPrice"] = 300,
|
||||
["name"] = "SG-550R"
|
||||
},
|
||||
["weapon_sw_fim92"] = {
|
||||
["category"] = "heavy",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 500,
|
||||
["name"] = "FIM-92"
|
||||
},
|
||||
["tacrp_sd_groza"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "ОЦ-14"
|
||||
},
|
||||
["tacrp_ifak"] = {
|
||||
["category"] = "tool7",
|
||||
["moneyPrice"] = 500,
|
||||
["supplyPrice"] = 50,
|
||||
["name"] = "Аптечка IFAK"
|
||||
},
|
||||
["tacrp_aug"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 250,
|
||||
["name"] = "AUG"
|
||||
},
|
||||
["weapon_sw_panzerfaust3"] = {
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 500,
|
||||
["maxCount"] = 1,
|
||||
["ammoType"] = "PanzerFaust3 Rocket",
|
||||
["category"] = "heavy",
|
||||
["maxAmmo"] = 1,
|
||||
["name"] = "Панцерфауст 3"
|
||||
},
|
||||
["tacrp_io_scarh"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "FN SCAR"
|
||||
},
|
||||
["tacrp_io_xm8car"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 12000,
|
||||
["supplyPrice"] = 250,
|
||||
["name"] = "XM8"
|
||||
},
|
||||
["tacrp_pa_makarov"] = {
|
||||
["category"] = "secondary",
|
||||
["moneyPrice"] = 200,
|
||||
["supplyPrice"] = 1,
|
||||
["name"] = "ПМ"
|
||||
},
|
||||
["tacrp_bekas"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 100,
|
||||
["name"] = "Bekas"
|
||||
},
|
||||
["weapon_cuff_elastic"] = {
|
||||
["category"] = "tool8",
|
||||
["moneyPrice"] = 300,
|
||||
["supplyPrice"] = 20,
|
||||
["name"] = "Наручники"
|
||||
},
|
||||
["tacrp_ex_hecate"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 17000,
|
||||
["supplyPrice"] = 300,
|
||||
["name"] = "PGM Hecate"
|
||||
},
|
||||
["tacrp_ex_glock"] = {
|
||||
["category"] = "secondary",
|
||||
["moneyPrice"] = 200,
|
||||
["supplyPrice"] = 20,
|
||||
["name"] = "Glock"
|
||||
},
|
||||
["tfa_ins2_aks_r"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 11000,
|
||||
["supplyPrice"] = 220,
|
||||
["name"] = "AKS-74U"
|
||||
},
|
||||
["tacrp_ak_ak74"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 50,
|
||||
["name"] = "АК-74"
|
||||
},
|
||||
["tacrp_io_sl8"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 9000,
|
||||
["supplyPrice"] = 180,
|
||||
["name"] = "SL8"
|
||||
},
|
||||
["tfa_ins2_moe_akm"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 12000,
|
||||
["supplyPrice"] = 240,
|
||||
["name"] = "AKM MOE"
|
||||
},
|
||||
["tacrp_io_val"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "АС ВАЛ"
|
||||
},
|
||||
["tacrp_skorpion"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 100,
|
||||
["name"] = "VZ-61"
|
||||
},
|
||||
["swep_drone_grenade"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 1000,
|
||||
["name"] = "Дрон с гранатой"
|
||||
},
|
||||
["tacrp_sd_pkm"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 15000,
|
||||
["supplyPrice"] = 300,
|
||||
["name"] = "ПКМ"
|
||||
},
|
||||
["weapon_sw_rpg28"] = {
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 500,
|
||||
["maxCount"] = 1,
|
||||
["ammoType"] = "rpg_round",
|
||||
["category"] = "heavy",
|
||||
["maxAmmo"] = 1,
|
||||
["name"] = "РПГ-28"
|
||||
},
|
||||
["tacrp_nade_flashbang"] = {
|
||||
["category"] = "grenade1",
|
||||
["moneyPrice"] = 200,
|
||||
["supplyPrice"] = 20,
|
||||
["name"] = "Светошумовая граната"
|
||||
},
|
||||
["v92_bf2_ammokit"] = {
|
||||
["category"] = "tool3",
|
||||
["moneyPrice"] = 1500,
|
||||
["supplyPrice"] = 150,
|
||||
["name"] = "Ящик с патронами"
|
||||
},
|
||||
["tacrp_io_vss"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 15000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "ВСС Винторез"
|
||||
},
|
||||
["tacrp_io_p226"] = {
|
||||
["category"] = "secondary",
|
||||
["moneyPrice"] = 200,
|
||||
["supplyPrice"] = 1,
|
||||
["name"] = "P226"
|
||||
},
|
||||
["tfa_blast_ksvk_cqb"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 15000,
|
||||
["supplyPrice"] = 300,
|
||||
["name"] = "KSvK 12.7"
|
||||
},
|
||||
["tacrp_io_k98"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 100,
|
||||
["name"] = "KAR98K"
|
||||
},
|
||||
["tacrp_superv"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 9000,
|
||||
["supplyPrice"] = 180,
|
||||
["name"] = "KRISS Vector"
|
||||
},
|
||||
["tacrp_nade_smoke"] = {
|
||||
["category"] = "grenade2",
|
||||
["moneyPrice"] = 150,
|
||||
["supplyPrice"] = 15,
|
||||
["name"] = "Дымовая граната"
|
||||
},
|
||||
["tacrp_sd_aac_hb"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "AAC Honey badger"
|
||||
},
|
||||
["tacrp_pa_vykhlop"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 17000,
|
||||
["supplyPrice"] = 350,
|
||||
["name"] = "ВСК Выхлоп"
|
||||
},
|
||||
["tacrp_sd_g3"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "G3A3"
|
||||
},
|
||||
["tfa_ins2_warface_cheytac_m200"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 17000,
|
||||
["supplyPrice"] = 340,
|
||||
["name"] = "Cheytac M200"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- ============================================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- DONATE WEAPONS (donate_only = true означает что доступно ТОЛЬКО через донат)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- Если donate_only НЕ указано или false, оружие может быть добавлено в spec/podr и будет платным
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- ============================================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- Патроны: тип -> {name, price, amount}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
ammo = {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- ["rifle"] = { name = "Патроны для винтовки", price = 10, amount = 30 },
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- Броня (опционально)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
armor = {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- amount: how much armor will be set on the player after purchase
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- supplyPrice: cost in faction supply points (keeps naming consistent with weapons)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
["armor_light"] = { name = "Легкая броня", supplyPrice = 50, moneyPrice = 0, amount = 25 },
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
["armor_medium"] = { name = "Средняя броня", supplyPrice = 75, moneyPrice = 0, amount = 50 },
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
["armor_heavy"] = { name = "Тяжелая броня", supplyPrice = 150, moneyPrice = 0, amount = 100 },
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
wardrobeBlacklist = {},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
patchIndex = 15,
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
patchMaterials = {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
"models/texture_vsu_1amb/path",
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
"models/texture_vsu_1amb/path2",
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
"models/texture_vsu_1amb/path3"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
wardrobeAccess = {},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
wardrobeStats = {},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
wardrobeCHands = {}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
64
garrysmod/gamemodes/militaryrp/plugins/arsenal/sh_plugin.lua
Normal file
64
garrysmod/gamemodes/militaryrp/plugins/arsenal/sh_plugin.lua
Normal file
@@ -0,0 +1,64 @@
|
||||
local PLUGIN = PLUGIN
|
||||
PLUGIN.name = "Arsenal"
|
||||
PLUGIN.author = "Refosel"
|
||||
PLUGIN.description = "Arsenal plugin (skeleton) — выдача оружия, патронов и управление очками снабжения."
|
||||
|
||||
ix.util.Include("sh_config.lua")
|
||||
ix.util.Include("cl_plugin.lua")
|
||||
ix.util.Include("sv_plugin.lua")
|
||||
|
||||
|
||||
ix.command.Add("test_arsenal", {
|
||||
description = "Test arsenal weapon availability.",
|
||||
OnRun = function(self, client)
|
||||
local plugin = ix.plugin.list and ix.plugin.list["arsenal"]
|
||||
if not plugin then return "Plugin not found" end
|
||||
|
||||
local char = client:GetCharacter()
|
||||
if not char then return "No character" end
|
||||
|
||||
client:ChatPrint("--- Arsenal Test for " .. client:Nick() .. " ---")
|
||||
local avail = plugin:GetAvailableWeapons(char)
|
||||
client:ChatPrint("Available weapons total: " .. table.Count(avail))
|
||||
for class, data in pairs(avail) do
|
||||
client:ChatPrint(string.format(" [%s] %s (Donate: %s)", class, data.name or "N/A", tostring(data.isDonateVersion or false)))
|
||||
end
|
||||
client:ChatPrint("--- End Test ---")
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("arsenal_supply_set", {
|
||||
description = "Set faction supply. Usage: arsenal_supply_set <amount> [faction id]",
|
||||
adminOnly = true,
|
||||
arguments = {
|
||||
ix.type.number,
|
||||
ix.type.number
|
||||
},
|
||||
OnRun = function(self, client, amount, faction)
|
||||
if CLIENT then return end
|
||||
local plugin = ix.plugin.list and ix.plugin.list["arsenal"]
|
||||
if not plugin then return client:Notify("Arsenal plugin not loaded") end
|
||||
|
||||
local fid = faction or client:GetCharacter():GetFaction()
|
||||
plugin:SetFactionSupply(fid, math.max(tonumber(amount) or 0, 0))
|
||||
client:Notify("Set supply for faction " .. tostring(fid) .. " to " .. tostring(plugin:GetFactionSupply(fid)))
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("arsenal_supply_add", {
|
||||
description = "Add (or subtract) supply for a faction. Usage: arsenal_supply_add <delta> [faction id]",
|
||||
adminOnly = true,
|
||||
arguments = {
|
||||
ix.type.number,
|
||||
ix.type.number
|
||||
},
|
||||
OnRun = function(self, client, delta, faction)
|
||||
if CLIENT then return end
|
||||
local plugin = ix.plugin.list and ix.plugin.list["arsenal"]
|
||||
if not plugin then return client:Notify("Arsenal plugin not loaded") end
|
||||
|
||||
local fid = faction or client:GetCharacter():GetFaction()
|
||||
plugin:AddFactionSupply(fid, tonumber(delta) or 0)
|
||||
client:Notify("Faction " .. tostring(fid) .. " supply now: " .. tostring(plugin:GetFactionSupply(fid)))
|
||||
end
|
||||
})
|
||||
751
garrysmod/gamemodes/militaryrp/plugins/arsenal/sv_plugin.lua
Normal file
751
garrysmod/gamemodes/militaryrp/plugins/arsenal/sv_plugin.lua
Normal file
@@ -0,0 +1,751 @@
|
||||
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
|
||||
Reference in New Issue
Block a user