924 lines
37 KiB
Lua
924 lines
37 KiB
Lua
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)
|