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

924 lines
37 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
local PLUGIN = PLUGIN
-- Хранение выбранного loadout для каждой техники
local selectedLoadouts = {}
-- Цветовая схема (такая же как в арсенале)
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
PLUGIN.clientFactionPoints = {}
PLUGIN.selectedLoadouts = {} -- Глобальное хранилище выбранных loadout'ов
-- Проверка доступности техники для игрока
function PLUGIN:CanPlayerAccessVehicle(vehicleConfig)
local character = LocalPlayer():GetCharacter()
if not character then return false end
local playerSubdivision = character:GetPodr()
-- Если нет ограничений по подразделениям - доступно всем
if not vehicleConfig.allowedPodr or not istable(vehicleConfig.allowedPodr) then
return true
end
-- Проверяем доступность для подразделения игрока
return vehicleConfig.allowedPodr[playerSubdivision] ~= nil
end
function PLUGIN:GetFactions()
if not self.cachedFactions then
if not self.config then
-- Fallback to global if self.config is missing for some reason
self.config = PLUGIN.config or {}
end
if not self.config or not istable(self.config) then
self.cachedFactions = {}
else
self.cachedFactions = self.config.getFactions and self.config.getFactions() or self.config.factions or {}
end
end
return self.cachedFactions
end
-- Сетевые сообщения
net.Receive("LVS_SyncFactionPoints", function()
local faction = net.ReadUInt(8)
local points = net.ReadUInt(32)
PLUGIN.clientFactionPoints[faction] = points
end)
-- Открытие меню наземной техники
net.Receive("LVS_OpenGroundMenu", function()
PLUGIN:OpenVehicleMenu("ground")
end)
-- Открытие меню воздушной техники
net.Receive("LVS_OpenAirMenu", function()
PLUGIN:OpenVehicleMenu("air")
end)
local function UpdateVehicleInfo(vehicleConfig, vehicleID, infoContent, modelPanel, vehicleName, vehicleDesc, vehiclePrice, vehicleCooldown, spawnBtn, factionPoints, vehicleType, returnBtn, loadoutPanel, updateLoadoutCallback)
if not vehicleConfig then return end
-- Инициализируем выбранный loadout если его нет (дефолтный = 1)
if not PLUGIN.selectedLoadouts[vehicleID] then
PLUGIN.selectedLoadouts[vehicleID] = 1
end
local faction = LocalPlayer():Team()
local factions = PLUGIN:GetFactions()
local factionConfig = factions and factions[faction]
local character = LocalPlayer():GetCharacter()
local playerSubdivision = character and character:GetPodr() or 1
-- Проверяем доступность по подразделению
local canSpawnBySubdivision = true
local subdivisionMessage = ""
-- Проверяем разрешенные подразделения и специализации
if vehicleConfig.allowedPodr and istable(vehicleConfig.allowedPodr) then
local playerSpec = character and character:GetSpec() or 1
local podrConfig = vehicleConfig.allowedPodr[playerSubdivision]
if podrConfig == nil then
-- Подразделение не в списке
canSpawnBySubdivision = false
elseif podrConfig == true then
-- Разрешено всем специализациям этого подразделения
canSpawnBySubdivision = true
elseif istable(podrConfig) then
-- Проверяем специализацию
canSpawnBySubdivision = table.HasValue(podrConfig, playerSpec)
end
if not canSpawnBySubdivision then
-- Получаем названия разрешенных подразделений и специализаций
local podrNames = {}
local factionTable = ix.faction.Get(faction)
if factionTable and factionTable.Podr and factionTable.Spec then
for podrID, config in pairs(vehicleConfig.allowedPodr) do
local podrData = factionTable.Podr[podrID]
if podrData and podrData.name then
if config == true then
table.insert(podrNames, podrData.name)
elseif istable(config) then
local specNames = {}
for _, specID in ipairs(config) do
local specData = factionTable.Spec[specID]
if specData and specData.name then
table.insert(specNames, specData.name)
end
end
if #specNames > 0 then
table.insert(podrNames, podrData.name .. " (" .. table.concat(specNames, ", ") .. ")")
end
end
end
end
end
if #podrNames > 0 then
subdivisionMessage = "Только: " .. table.concat(podrNames, "; ")
else
subdivisionMessage = "Недоступно для вашего подразделения"
end
end
end
-- Показываем элементы
modelPanel:SetVisible(true)
vehicleName:SetVisible(true)
vehicleDesc:SetVisible(true)
vehiclePrice:SetVisible(true)
vehicleCooldown:SetVisible(true)
spawnBtn:SetVisible(true)
returnBtn:SetVisible(true)
-- Устанавливаем модель
modelPanel:SetModel(vehicleConfig.model)
local ent = modelPanel.Entity
if IsValid(ent) then
local center = ent:OBBCenter()
local distance = ent:BoundingRadius() * 1.5
modelPanel:SetLookAt(center)
modelPanel:SetCamPos(center + Vector(distance, distance, distance))
modelPanel:SetFOV(50)
end
-- Устанавливаем текстовую информацию
vehicleName:SetText(vehicleConfig.name)
vehicleDesc:SetText(vehicleConfig.description)
vehiclePrice:SetText("Стоимость: " .. vehicleConfig.price .. " очков техники")
-- Проверяем кд и доступность
local vehiclesData = character and character:GetData("lvs_vehicles", {}) or {}
local lastSpawn = vehiclesData[vehicleID]
local cooldown = lastSpawn and lastSpawn > os.time() and (lastSpawn - os.time()) or 0
-- Проверяем, есть ли активная техника этого типа
local hasActiveVehicle = PLUGIN:HasActiveVehicle(vehicleID, vehicleType)
local refundAmount = math.floor(vehicleConfig.price * 0.8)
-- Настраиваем кнопку возврата
if hasActiveVehicle then
returnBtn:SetEnabled(true)
returnBtn:SetText("")
returnBtn.Paint = function(s, w, h)
local col = s:IsHovered() and Color(185, 60, 60) or Color(165, 85, 60)
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("ВЕРНУТЬ (" .. refundAmount .. " очков)", "ixSmallFont", w/2, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
-- Отправляем запрос на возврат техники при нажатии кнопки
returnBtn.DoClick = function()
local veh = PLUGIN:GetActiveVehicle(vehicleID, vehicleType)
if IsValid(veh) then
net.Start("LVS_RequestReturn")
net.WriteUInt(veh:EntIndex(), 16)
net.SendToServer()
else
LocalPlayer():Notify("Активная техника не найдена")
end
end
else
returnBtn:SetEnabled(false)
returnBtn:SetText("")
returnBtn.Paint = function(s, w, h)
draw.RoundedBox(6, 0, 0, w, h, Color(60, 60, 60))
surface.SetDrawColor(COLOR_BG_DARK)
surface.DrawOutlinedRect(0, 0, w, h, 2)
draw.SimpleText("НЕТ АКТИВНОЙ ТЕХНИКИ", "ixSmallFont", w/2, h/2, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
end
if not canSpawnBySubdivision then
vehicleCooldown:SetText(subdivisionMessage)
vehicleCooldown:SetTextColor(COLOR_DANGER)
spawnBtn:SetEnabled(false)
spawnBtn:SetText("")
spawnBtn.Paint = function(s, w, h)
draw.RoundedBox(6, 0, 0, w, h, Color(60, 60, 60))
surface.SetDrawColor(COLOR_BG_DARK)
surface.DrawOutlinedRect(0, 0, w, h, 2)
draw.SimpleText("НЕДОСТУПНО", "ixSmallFont", w/2, h/2, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
elseif cooldown > 0 then
local minutes = math.floor(cooldown / 60)
local seconds = math.floor(cooldown % 60)
vehicleCooldown:SetText("Перезарядка: " .. string.format("%02d:%02d", minutes, seconds))
vehicleCooldown:SetTextColor(COLOR_WARNING)
spawnBtn:SetEnabled(false)
spawnBtn:SetText("")
spawnBtn.Paint = function(s, w, h)
draw.RoundedBox(6, 0, 0, w, h, Color(60, 60, 60))
surface.SetDrawColor(COLOR_BG_DARK)
surface.DrawOutlinedRect(0, 0, w, h, 2)
draw.SimpleText("В КД", "ixSmallFont", w/2, h/2, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
elseif factionPoints < vehicleConfig.price then
vehicleCooldown:SetText("Недостаточно очков техники")
vehicleCooldown:SetTextColor(COLOR_WARNING)
spawnBtn:SetEnabled(false)
spawnBtn:SetText("")
spawnBtn.Paint = function(s, w, h)
draw.RoundedBox(6, 0, 0, w, h, Color(60, 60, 60))
surface.SetDrawColor(COLOR_BG_DARK)
surface.DrawOutlinedRect(0, 0, w, h, 2)
draw.SimpleText("НЕТ ОЧКОВ", "ixSmallFont", w/2, h/2, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
else
vehicleCooldown:SetText("Готов к вызову")
vehicleCooldown:SetTextColor(COLOR_ACCENT)
spawnBtn:SetEnabled(true)
spawnBtn:SetText("")
spawnBtn.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
spawnBtn.DoClick = function()
local loadoutID = PLUGIN.selectedLoadouts[vehicleID] or 1
print("[LVS] Запрос спавна техники " .. vehicleConfig.name .. " с loadout ID: " .. loadoutID)
PLUGIN:RequestSpawnVehicle(vehicleID, vehicleType, loadoutID)
end
end
-- Обновляем панель loadout если она передана
if IsValid(loadoutPanel) and updateLoadoutCallback then
updateLoadoutCallback(vehicleConfig, vehicleID, loadoutPanel)
end
end
function PLUGIN:GetActiveVehicle(vehicleID, vehicleType)
local ply = LocalPlayer()
if not IsValid(ply) then return nil end
for _, ent in ipairs(ents.GetAll()) do
if IsValid(ent) then
local nwID = ent:GetNWString("LVS_VehicleID", "")
local nwType = ent:GetNWString("LVS_VehicleType", "")
local nwOwner = ent:GetNWString("LVS_OwnerSteamID", "")
if nwID == tostring(vehicleID) and nwType == tostring(vehicleType) then
if ply:IsAdmin() or ply:SteamID() == nwOwner then
return ent
end
end
end
end
return nil
end
-- Функция обновления кд
function PLUGIN:UpdateVehicleCooldown(panel, vehicleID)
local character = LocalPlayer():GetCharacter()
if not character then return end
local vehicles = character:GetData("lvs_vehicles", {})
local lastSpawn = vehicles[vehicleID]
if lastSpawn and lastSpawn > os.time() then
panel.cooldown = lastSpawn - os.time()
timer.Create("LVS_Cooldown_" .. vehicleID, 1, 0, function()
if not IsValid(panel) then
timer.Remove("LVS_Cooldown_" .. vehicleID)
return
end
panel.cooldown = panel.cooldown - 1
if panel.cooldown <= 0 then
panel.cooldown = 0
timer.Remove("LVS_Cooldown_" .. vehicleID)
end
end)
else
panel.cooldown = 0
end
end
-- Основная функция создания меню в стиле Military RP
function PLUGIN:OpenVehicleMenu(vehicleType)
-- Если окно уже открыто — закроем старое, чтобы не было дубликатов
if IsValid(PLUGIN._vehicleMenuFrame) then
PLUGIN._vehicleMenuFrame:Remove()
PLUGIN._vehicleMenuFrame = nil
end
local scrW, scrH = ScrW(), ScrH()
local faction = LocalPlayer():Team()
local factions = PLUGIN:GetFactions()
local factionConfig = factions and factions[faction]
if not factionConfig then
return
end
local vehicles = vehicleType == "ground" and factionConfig.groundVehicles or factionConfig.airVehicles
-- Фильтруем технику по доступности
local availableVehicles = {}
for vehicleID, vehicleConfig in pairs(vehicles) do
if self:CanPlayerAccessVehicle(vehicleConfig) then
availableVehicles[vehicleID] = vehicleConfig
end
end
-- Если нет доступной техники - не открываем меню
if table.Count(availableVehicles) == 0 then
LocalPlayer():Notify("Техника вам не доступна")
return
end
local frame = vgui.Create("DFrame")
frame:SetSize(scrW, scrH)
frame:SetPos(0, 0)
frame:SetTitle("")
frame:SetDraggable(false)
frame:ShowCloseButton(false)
frame:MakePopup()
local factionPoints = self.clientFactionPoints[faction] or 0
-- Градиентный фон с зеленым оттенком
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
-- Заголовок
local title = vehicleType == "ground" and "◆ НАЗЕМНАЯ ТЕХНИКА ◆" or "◆ ВОЗДУШНАЯ ТЕХНИКА ◆"
local subtitle = vehicleType == "ground" and "СИСТЕМА ВЫЗОВА ТЕХНИКИ" or "СИСТЕМА ВЫЗОВА АВИАЦИИ"
draw.SimpleText(title, "ixMenuButtonFont", w/2, 35, COLOR_ACCENT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
draw.SimpleText(subtitle, "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(300, 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)
draw.SimpleText("ОЧКИ ТЕХНИКИ", "ixSmallFont", 20, 12, COLOR_TEXT_SECONDARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
draw.SimpleText(factionPoints, "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 colWidth = (scrW - 120) / 2
local startY = 205
-- Панель списка техники
local vehiclesPanel = vgui.Create("DPanel", frame)
vehiclesPanel:SetSize(colWidth, scrH - startY - 40)
vehiclesPanel:SetPos(40, startY)
vehiclesPanel.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 vehiclesHeader = vgui.Create("DPanel", vehiclesPanel)
vehiclesHeader:SetSize(vehiclesPanel:GetWide(), 45)
vehiclesHeader:SetPos(0, 0)
vehiclesHeader.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 infoPanel2 = vgui.Create("DPanel", frame)
infoPanel2:SetSize(colWidth, scrH - startY - 40)
infoPanel2:SetPos(scrW - colWidth - 40, startY)
infoPanel2.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 infoHeader = vgui.Create("DPanel", infoPanel2)
infoHeader:SetSize(infoPanel2:GetWide(), 45)
infoHeader:SetPos(0, 0)
infoHeader.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 vehiclesScroll = vgui.Create("DScrollPanel", vehiclesPanel)
vehiclesScroll:SetSize(vehiclesPanel:GetWide() - 20, vehiclesPanel:GetTall() - 60)
vehiclesScroll:SetPos(10, 50)
local sbar = vehiclesScroll: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 infoContent = vgui.Create("DPanel", infoPanel2)
infoContent:SetSize(infoPanel2:GetWide() - 20, infoPanel2:GetTall() - 60)
infoContent:SetPos(10, 50)
infoContent.selectedVehicle = nil
infoContent.Paint = function(s, w, h)
draw.RoundedBox(6, 0, 0, w, h, COLOR_BG_LIGHT)
if not s.selectedVehicle then
draw.SimpleText("⚠ ВЫБЕРИТЕ ТЕХНИКУ", "ixSmallFont", w/2, h/2, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
end
-- Модель техники
local modelPanel = vgui.Create("DModelPanel", infoContent)
modelPanel:SetSize(400, 200)
modelPanel:SetPos(10, 10)
modelPanel:SetVisible(false)
-- Информация о технике
local vehicleName = vgui.Create("DLabel", infoContent)
vehicleName:SetFont("ixSmallFont")
vehicleName:SetTextColor(color_white)
vehicleName:SetPos(10, 220)
vehicleName:SetSize(400, 20)
vehicleName:SetVisible(false)
local vehicleDesc = vgui.Create("DLabel", infoContent)
vehicleDesc:SetFont("ixSmallFont")
vehicleDesc:SetTextColor(Color(200, 200, 200))
vehicleDesc:SetPos(10, 245)
vehicleDesc:SetSize(400, 40)
vehicleDesc:SetVisible(false)
local vehiclePrice = vgui.Create("DLabel", infoContent)
vehiclePrice:SetFont("ixSmallFont")
vehiclePrice:SetTextColor(color_white)
vehiclePrice:SetPos(10, 290)
vehiclePrice:SetSize(400, 20)
vehiclePrice:SetVisible(false)
local vehicleCooldown = vgui.Create("DLabel", infoContent)
vehicleCooldown:SetFont("ixSmallFont")
vehicleCooldown:SetTextColor(color_white)
vehicleCooldown:SetPos(10, 315)
vehicleCooldown:SetSize(400, 20)
vehicleCooldown:SetVisible(false)
-- Кнопка вызова
local spawnBtn = vgui.Create("DButton", infoContent)
spawnBtn:SetSize(200, 35)
spawnBtn:SetPos(10, 350)
spawnBtn:SetText("")
spawnBtn:SetVisible(false)
-- Кнопка возврата
local returnBtn = vgui.Create("DButton", infoContent)
returnBtn:SetSize(200, 35)
returnBtn:SetPos(220, 350)
returnBtn:SetText("")
returnBtn:SetVisible(false)
-- Используем глобальное хранилище selectedLoadouts
local selectedLoadouts = PLUGIN.selectedLoadouts
-- Создаем панель выбора loadout (скрыта по умолчанию)
local loadoutPanel = vgui.Create("DPanel", infoContent)
loadoutPanel:SetSize(infoContent:GetWide() - 20, 120)
loadoutPanel:SetPos(10, 400)
loadoutPanel:SetVisible(false)
loadoutPanel.Paint = function(s, w, h)
draw.RoundedBox(6, 0, 0, w, h, ColorAlpha(COLOR_BG_DARK, 200))
surface.SetDrawColor(COLOR_PRIMARY)
surface.DrawOutlinedRect(0, 0, w, h, 2)
draw.SimpleText("⚙ ВООРУЖЕНИЕ:", "ixSmallFont", 10, 8, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
end
-- Функция обновления панели loadout
local function UpdateLoadoutPanel(vehicleConfig, vehicleID, panel)
if not IsValid(panel) then return end
for _, child in pairs(panel:GetChildren()) do
child:Remove()
end
local loadoutConfig = vehicleConfig.loadout
if not istable(loadoutConfig) or table.Count(loadoutConfig) == 0 then
panel:SetVisible(false)
return
end
panel:SetVisible(true)
local btnWidth = 130
local btnHeight = 80
local gap = 10
local startX = 10
local startY = 30
-- соберём пары в массив и отсортируем по ID, чтобы кнопки шли в правильном порядке
local entries = {}
for loadoutName, loadoutID in pairs(loadoutConfig) do
if isnumber(loadoutID) then
table.insert(entries, {name = loadoutName, id = loadoutID})
end
end
table.sort(entries, function(a, b) return a.id < b.id end)
-- вставим пустые слоты для пропущенных ID, чтобы кнопки располагались на своих позициях
local filled = {}
local nextIdx = 1
local maxID = entries[#entries] and entries[#entries].id or 0
for id = 1, maxID do
if entries[nextIdx] and entries[nextIdx].id == id then
table.insert(filled, entries[nextIdx])
nextIdx = nextIdx + 1
else
table.insert(filled, {name = "", id = id})
end
end
for idx, entry in ipairs(filled) do
local btn = vgui.Create("DButton", panel)
btn:SetSize(btnWidth, btnHeight)
btn:SetPos(startX + (idx - 1) * (btnWidth + gap), startY)
btn:SetText("")
btn.variantIndex = idx
btn.loadoutID = entry.id
btn.loadoutName = tostring(entry.name)
btn.Paint = function(s, w, h)
local isSelected = PLUGIN.selectedLoadouts[vehicleID] == s.loadoutID
local bgColor = isSelected and COLOR_PRIMARY or COLOR_BG_MEDIUM
if s:IsHovered() and not isSelected then
bgColor = ColorAlpha(COLOR_PRIMARY, 100)
end
draw.RoundedBox(4, 0, 0, w, h, bgColor)
if isSelected then
surface.SetDrawColor(COLOR_ACCENT)
surface.DrawOutlinedRect(0, 0, w, h, 3)
else
surface.SetDrawColor(COLOR_BORDER)
surface.DrawOutlinedRect(0, 0, w, h, 1)
end
draw.SimpleText(s.loadoutName or ("Вариант " .. s.variantIndex), "ixSmallFont", w/2, 12, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP)
draw.SimpleText("ID: " .. tostring(s.loadoutID), "DermaDefault", w/2, 50, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER)
if isSelected then
draw.SimpleText("", "ixMenuButtonFont", w/2, h - 20, COLOR_ACCENT, TEXT_ALIGN_CENTER)
end
end
btn.DoClick = function()
print("[LVS] Пользователь выбрал loadout '" .. btn.loadoutName .. "' (ID=" .. btn.loadoutID .. ") для " .. vehicleConfig.name)
PLUGIN.selectedLoadouts[vehicleID] = btn.loadoutID
for _, child in pairs(panel:GetChildren()) do
child:Remove()
end
UpdateLoadoutPanel(vehicleConfig, vehicleID, panel)
end
end
end
-- Привяжем frame к плагину, чтобы можно было предотвращать дубликаты
PLUGIN._vehicleMenuFrame = frame
-- Функция создания строки техники
local function CreateVehicleRow(parent, vehicleID, vehicleConfig)
local row = vgui.Create("DButton", parent)
row:SetSize(parent:GetWide() - 10, 70)
row:SetText("")
row.vehicleID = vehicleID
row.vehicleConfig = vehicleConfig
local isHovered = false
row.Paint = function(s, w, h)
local isSelected = infoContent.selectedVehicle == vehicleID
local canAfford = factionPoints >= vehicleConfig.price
local cooldown = s.cooldown or 0
local hasActive = PLUGIN:HasActiveVehicle(vehicleID, vehicleType)
-- Фон строки
local bgColor = isSelected and COLOR_PRIMARY_DARK or COLOR_BG_LIGHT
if isHovered and not isSelected then
bgColor = Color(bgColor.r + 10, bgColor.g + 15, bgColor.b + 10)
end
draw.RoundedBox(6, 0, 0, w, h, bgColor)
-- Боковая полоска
local statusColor = hasActive and Color(56, 142, 200) or (canAfford and COLOR_PRIMARY or COLOR_DANGER)
draw.RoundedBoxEx(6, 0, 0, 5, h, statusColor, true, false, true, false)
-- Рамка
surface.SetDrawColor(isSelected and COLOR_ACCENT 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
-- Название техники
draw.SimpleText(vehicleConfig.name, "ixSmallFont", 15, 12, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT)
-- Цена
local priceColor = canAfford and COLOR_ACCENT or COLOR_WARNING
draw.SimpleText(vehicleConfig.price .. " очков", "ixSmallFont", 15, 35, priceColor, TEXT_ALIGN_LEFT)
-- Статус
local statusText = ""
local statusTextColor = COLOR_TEXT_SECONDARY
if hasActive then
statusText = "● АКТИВНА"
statusTextColor = Color(100, 180, 255)
elseif cooldown > 0 then
local minutes = math.floor(cooldown / 60)
local seconds = math.floor(cooldown % 60)
statusText = "КД: " .. string.format("%02d:%02d", minutes, seconds)
statusTextColor = COLOR_WARNING
elseif not canAfford then
statusText = "НЕТ ОЧКОВ"
statusTextColor = COLOR_WARNING
else
statusText = "● ГОТОВ"
statusTextColor = COLOR_ACCENT
end
draw.SimpleText(statusText, "ixSmallFont", w - 15, h/2, statusTextColor, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER)
end
row.OnCursorEntered = function() isHovered = true end
row.OnCursorExited = function() isHovered = false end
row.DoClick = function(s)
infoContent.selectedVehicle = vehicleID
UpdateVehicleInfo(vehicleConfig, vehicleID, infoContent, modelPanel, vehicleName, vehicleDesc, vehiclePrice, vehicleCooldown, spawnBtn, factionPoints, vehicleType, returnBtn, loadoutPanel, UpdateLoadoutPanel)
end
-- Обновляем кд
PLUGIN:UpdateVehicleCooldown(row, vehicleID)
return row
end
-- Создаем строки с техникой (только доступной)
for vehicleID, vehicleConfig in SortedPairsByMemberValue(availableVehicles, "price") do
local row = CreateVehicleRow(vehiclesScroll, vehicleID, vehicleConfig)
row:Dock(TOP)
row:DockMargin(0, 0, 0, 5)
end
-- Функция обновления интерфейса
local function UpdateInterface()
if not IsValid(frame) then return end
local currentPoints = PLUGIN.clientFactionPoints[faction] or 0
factionPoints = currentPoints
-- Обновляем отображение очков
if IsValid(pointsPanel) then
pointsPanel.Paint = function(s, w, h)
surface.SetDrawColor(Color(23, 23, 23))
surface.DrawRect(0, 0, w, h)
surface.SetDrawColor(Color(1, 67, 29))
surface.DrawOutlinedRect(0, 0, w, h, 1)
draw.SimpleText("ОЧКИ ТЕХНИКИ", "ixSmallFont", w/2, 11, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
draw.SimpleText(currentPoints, "ixSmallFont", w/2, 28, Color(100, 255, 100), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
end
-- Обновляем информацию о выбранной технике
if infoContent.selectedVehicle then
local vehicleConfig = availableVehicles[infoContent.selectedVehicle]
if vehicleConfig then
UpdateVehicleInfo(vehicleConfig, infoContent.selectedVehicle, infoContent, modelPanel, vehicleName, vehicleDesc, vehiclePrice, vehicleCooldown, spawnBtn, currentPoints, vehicleType, returnBtn)
end
end
end
-- Таймер для обновления интерфейса
timer.Create("LVS_InterfaceUpdate_" .. vehicleType, 1, 0, function()
if not IsValid(frame) then
timer.Remove("LVS_InterfaceUpdate_" .. vehicleType)
return
end
UpdateInterface()
end)
-- Автоматически выбираем первую технику
timer.Simple(0.1, function()
if IsValid(vehiclesScroll) and vehiclesScroll:GetCanvas():GetChildren()[1] then
local firstRow = vehiclesScroll:GetCanvas():GetChildren()[1]
if firstRow.DoClick then
firstRow:DoClick()
end
end
end)
frame.OnCursorEntered = function(s)
s:MoveToFront()
end
frame.OnClose = function()
timer.Remove("LVS_InterfaceUpdate_" .. vehicleType)
if PLUGIN._vehicleMenuFrame == frame then
PLUGIN._vehicleMenuFrame = nil
end
end
end
-- Запрос спавна техники
function PLUGIN:RequestSpawnVehicle(vehicleID, vehicleType, loadoutVariant)
print("[LVS] Отправка запроса спавна на сервер - vehicleID: " .. vehicleID .. ", loadoutVariant: " .. (loadoutVariant or 0))
net.Start("LVS_RequestSpawn")
net.WriteString(vehicleID)
net.WriteString(vehicleType)
net.WriteUInt(loadoutVariant or 0, 8)
net.SendToServer()
end
-- Запрос возврата техники
function PLUGIN:RequestReturnVehicle(vehicleID, vehicleType)
local vehicle = self:GetActiveVehicle(vehicleID, vehicleType)
if IsValid(vehicle) then
net.Start("LVS_RequestReturn")
net.WriteUInt(vehicle:EntIndex(), 16)
net.SendToServer()
else
LocalPlayer():Notify("Активная техника не найдена")
end
end
net.Receive("LVS_SyncVehicleInfo", function()
local vehicle = net.ReadEntity()
local ownerSteamID = net.ReadString()
local vehicleID = net.ReadString()
local vehicleType = net.ReadString()
if IsValid(vehicle) then
vehicle.lvsVehicleInfo = {
ownerSteamID = ownerSteamID,
vehicleID = vehicleID,
vehicleType = vehicleType
}
end
end)
-- Получаем таблицу моделей от сервера и подставляем в конфиг при отсутствии
net.Receive("LVS_SyncVehicleModels", function()
local mapping = net.ReadTable()
if not istable(mapping) then return end
for factionID, data in pairs(mapping) do
local factionConfig = PLUGIN:GetFactions()[factionID]
if not factionConfig then continue end
if istable(data.ground) then
for id, mdl in pairs(data.ground) do
if factionConfig.groundVehicles and factionConfig.groundVehicles[id] then
if not factionConfig.groundVehicles[id].model or factionConfig.groundVehicles[id].model == "" then
factionConfig.groundVehicles[id].model = mdl
end
end
end
end
if istable(data.air) then
for id, mdl in pairs(data.air) do
if factionConfig.airVehicles and factionConfig.airVehicles[id] then
if not factionConfig.airVehicles[id].model or factionConfig.airVehicles[id].model == "" then
factionConfig.airVehicles[id].model = mdl
end
end
end
end
end
end)