add sborka

This commit is contained in:
2026-03-31 10:27:04 +03:00
commit f5e5f56c84
2345 changed files with 382127 additions and 0 deletions

View File

@@ -0,0 +1,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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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 = {}
}

View 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
})

View 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