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,923 @@
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)

View File

@@ -0,0 +1,35 @@
include("shared.lua")
function ENT:Draw()
self:DrawModel()
local pos = self:GetPos()
local ang = self:GetAngles()
pos = pos + ang:Up() * 40
ang:RotateAroundAxis(ang:Right(), -90)
ang:RotateAroundAxis(ang:Up(), 90)
cam.Start3D2D(pos, ang, 0.15)
local terminalType = self:GetTerminalType()
local title = "Терминал техники"
if terminalType == "ground" then
title = "НАЗЕМНАЯ ТЕХНИКА"
elseif terminalType == "air" then
title = "ВОЗДУШНАЯ ТЕХНИКА"
end
draw.SimpleTextOutlined(
title,
"DermaLarge",
0, 0,
Color(255, 255, 255),
TEXT_ALIGN_CENTER,
TEXT_ALIGN_CENTER,
2,
Color(0, 0, 0)
)
cam.End3D2D()
end

View File

@@ -0,0 +1,37 @@
AddCSLuaFile("cl_init.lua")
AddCSLuaFile("shared.lua")
include("shared.lua")
function ENT:Initialize()
self:SetModel(self:GetModel() or "models/props_c17/oildrum001.mdl")
self:PhysicsInit(SOLID_VPHYSICS)
self:SetMoveType(MOVETYPE_VPHYSICS)
self:SetSolid(SOLID_VPHYSICS)
self:SetUseType(SIMPLE_USE)
local phys = self:GetPhysicsObject()
if phys:IsValid() then
phys:Wake()
end
end
function ENT:Use(activator, caller)
if not IsValid(activator) or not activator:IsPlayer() then return end
local vehiclePlugin = ix.plugin.list["vehicles"]
if vehiclePlugin and vehiclePlugin.GetFactionPoints then
net.Start("LVS_SyncFactionPoints")
net.WriteUInt(activator:Team(), 8)
net.WriteUInt(vehiclePlugin:GetFactionPoints(activator:Team()) or 0, 32)
net.Send(activator)
end
local terminalType = self:GetTerminalType()
if terminalType == "ground" then
net.Start("LVS_OpenGroundMenu")
net.Send(activator)
elseif terminalType == "air" then
net.Start("LVS_OpenAirMenu")
net.Send(activator)
end
end

View File

@@ -0,0 +1,129 @@
ENT.Type = "anim"
ENT.Base = "base_gmodentity"
ENT.PrintName = "Vehicle Terminal"
ENT.Author = "RefoselDev"
ENT.Category = "[FT] Система техники"
ENT.Spawnable = false
ENT.AdminSpawnable = true
function ENT:SetupDataTables()
self:NetworkVar("String", 0, "TerminalType")
end
scripted_ents.Register({
Type = "anim",
Base = "base_gmodentity",
PrintName = "Терминал наземной техники",
Category = "[FT] Система техники",
Author = "RefoselDev",
Spawnable = true,
AdminOnly = true,
bNoPersist = true,
Model = "models/props_c17/oildrum001.mdl",
SetupDataTables = function(self)
self:NetworkVar("String", 0, "TerminalType")
end,
Initialize = function(self)
if (SERVER) then
self:SetModel(self.Model)
self:PhysicsInit(SOLID_VPHYSICS)
self:SetMoveType(MOVETYPE_VPHYSICS)
self:SetSolid(SOLID_VPHYSICS)
self:SetUseType(SIMPLE_USE)
self:SetTerminalType("ground")
local phys = self:GetPhysicsObject()
if phys:IsValid() then
phys:Wake()
phys:EnableMotion(false)
end
end
end,
Use = function(self, activator, caller)
if (SERVER) then
if not IsValid(activator) or not activator:IsPlayer() then return end
local vehiclePlugin = ix.plugin.list["vehicles"]
if vehiclePlugin and vehiclePlugin.GetFactionPoints then
net.Start("LVS_SyncFactionPoints")
net.WriteUInt(activator:Team(), 8)
net.WriteUInt(vehiclePlugin:GetFactionPoints(activator:Team()) or 0, 32)
net.Send(activator)
end
net.Start("LVS_OpenGroundMenu")
net.Send(activator)
end
end,
Draw = function(self)
self:DrawModel()
local pos = self:GetPos() + Vector(0, 0, 80)
local ang = Angle(0, LocalPlayer():EyeAngles().y - 90, 90)
cam.Start3D2D(pos, ang, 0.15)
draw.SimpleText("НАЗЕМНАЯ ТЕХНИКА", "DermaLarge", 0, 0, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
cam.End3D2D()
end
}, "ix_vehicle_terminal_ground")
scripted_ents.Register({
Type = "anim",
Base = "base_gmodentity",
PrintName = "Терминал воздушной техники",
Category = "[FT] Система техники",
Author = "RefoselDev",
Spawnable = true,
AdminOnly = true,
bNoPersist = true,
Model = "models/props_c17/oildrum001.mdl",
SetupDataTables = function(self)
self:NetworkVar("String", 0, "TerminalType")
end,
Initialize = function(self)
if (SERVER) then
self:SetModel(self.Model)
self:PhysicsInit(SOLID_VPHYSICS)
self:SetMoveType(MOVETYPE_VPHYSICS)
self:SetSolid(SOLID_VPHYSICS)
self:SetUseType(SIMPLE_USE)
self:SetTerminalType("air")
local phys = self:GetPhysicsObject()
if phys:IsValid() then
phys:Wake()
phys:EnableMotion(false)
end
end
end,
Use = function(self, activator, caller)
if (SERVER) then
if not IsValid(activator) or not activator:IsPlayer() then return end
local vehiclePlugin = ix.plugin.list["vehicles"]
if vehiclePlugin and vehiclePlugin.GetFactionPoints then
net.Start("LVS_SyncFactionPoints")
net.WriteUInt(activator:Team(), 8)
net.WriteUInt(vehiclePlugin:GetFactionPoints(activator:Team()) or 0, 32)
net.Send(activator)
end
net.Start("LVS_OpenAirMenu")
net.Send(activator)
end
end,
Draw = function(self)
self:DrawModel()
if (not CLIENT) then return end
local pos = self:GetPos() + self:GetUp() * 100
local ang = LocalPlayer():EyeAngles()
ang:RotateAroundAxis(ang:Right(), 90)
ang:RotateAroundAxis(ang:Up(), -90)
cam.Start3D2D(pos, ang, 0.1)
draw.SimpleText("ВОЗДУШНАЯ ТЕХНИКА", "DermaLarge", 0, 0, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
cam.End3D2D()
end
}, "ix_vehicle_terminal_air")

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,40 @@
PLUGIN.name = "LVS Vehicles System"
PLUGIN.author = "RefoselDev"
PLUGIN.description = "Система вызова LVS техники за очки"
ix.util.Include("sh_config.lua", "shared")
ix.util.Include("cl_plugin.lua", "client")
ix.util.Include("sv_plugin.lua", "server")
-- Функция проверки активной техники
function PLUGIN:HasActiveVehicle(vehicleID, vehicleType)
local ply = LocalPlayer()
if not IsValid(ply) then return false end
-- Ищем активную технику игрока
for _, ent in pairs(ents.GetAll()) do
if IsValid(ent) and ent.lvsVehicleInfo then
local info = ent.lvsVehicleInfo
if info.vehicleID == vehicleID and info.vehicleType == vehicleType then
-- Проверяем владельца или права админа
if ply:IsAdmin() or ply:SteamID() == info.ownerSteamID then
return true
end
end
end
-- fallback: проверим NWVars если lvsVehicleInfo ещё не синхронизировалось
if IsValid(ent) then
local nwOwner = ent:GetNWString("LVS_OwnerSteamID", "")
local nwID = ent:GetNWString("LVS_VehicleID", "")
local nwType = ent:GetNWString("LVS_VehicleType", "")
if nwID == tostring(vehicleID) and nwType == tostring(vehicleType) and nwOwner ~= "" then
if ply:IsAdmin() or ply:SteamID() == nwOwner then
return true
end
end
end
end
return false
end

File diff suppressed because it is too large Load Diff