include("shared.lua") -- === НАСТРОЙКА ИКОНОК, НАЗВАНИЙ И ОПИСАНИЙ (ARMA STYLE) === local CUSTOM_ICONS_BG = { ["shirt_and_jaket"] = { default = Material("wardrobe_ui/body.png", "noclamp smooth"), }, ["helm"] = { -- default = Material("wardrobe_ui/helm_icon.png", "noclamp smooth"), } } local CUSTOM_ICONS_SKIN = { -- default = Material("wardrobe_ui/skin_bg.png", "noclamp smooth"), } local CUSTOM_NAMES_BG = { ["shirt_and_jaket"] = { [0] = "Стандартная форма", [1] = "Закатанные рукава", }, ["helm"] = { [0] = "Без шлема", } } local CUSTOM_DESC_BG = { ["shirt_and_jaket"] = { [0] = "Полевая форма из плотного материала.\nОбеспечивает базовую маскировку.\n\n1.2kg", [1] = "Облегченный вариант формы для жаркого климата.\nСнижает перегрев бойца.\n\n1.0kg", }, ["helm"] = { [0] = "Отсутствие головного убора.\nНе предоставляет баллистической защиты.\n\n0.0kg", } } local CUSTOM_NAMES_SKIN = { [0] = "Лесной камуфляж (Woodland)", [1] = "Пустынный камуфляж (Desert)", } local CUSTOM_DESC_SKIN = { [0] = "Стандартный лесной паттерн для умеренного климата.\n\nКамуфляж", [1] = "Песочный паттерн для засушливых регионов.\n\nКамуфляж", } -- ======================== local C_BG_D = Color(3, 5, 4) local C_BG_M = Color(8, 12, 10) local C_BG_L = Color(12, 18, 14) local C_PRI = Color(27, 94, 32) local C_PRI_H = Color(33, 110, 38) local C_ACC = Color(56, 102, 35) local C_ACC_H = Color(66, 120, 45) local C_BDR = Color(46, 125, 50, 80) local C_TXT_P = Color(165, 214, 167) local C_GRAD = Color(27, 94, 32, 15) local C_WARN = Color(150, 40, 40) local C_WARN_H = Color(180, 50, 50) local C_WHITE = Color(255, 255, 255, 200) local C_ARMA_TOOLTIP_BG = Color(35, 35, 35, 240) local C_ARMA_SLOT = Color(40, 40, 40, 255) local C_ARMA_SLOT_HOVER = Color(60, 60, 60, 255) local C_ARMA_SLOT_EMPTY = Color(20, 20, 20, 150) local C_ARMA_SEL = Color(194, 138, 74, 255) local C_ARMA_TEXT = Color(180, 180, 180, 255) local bgMat = Material("materials/wardrobe_ui/fon.jpg", "noclamp smooth") -- ===================================================================== -- ПАНЕЛЬ ОБЫЧНОГО ИГРОКА -- ===================================================================== net.Receive("SandboxWardrobeOpen", function() local model = net.ReadString() local bodygroups = net.ReadTable() local availableSkins = net.ReadTable() local currentBodygroups = net.ReadTable() local currentSkin = net.ReadUInt(8) local unlockedBodygroups = net.ReadTable() local serverPresets = net.ReadTable() local scrW, scrH = ScrW(), ScrH() local fW = math.Clamp(scrW * 0.62, 950, 1600) local fH = math.Clamp(scrH * 0.62, 600, 1000) local leftW = 230 local rightW = math.Clamp(fW * 0.38, 350, 550) local midW = fW - leftW - rightW - 80 local midX = 40 + leftW local rightX = midX + midW + 20 local frame = vgui.Create("DFrame") frame:SetSize(fW, fH) frame:Center() frame:SetTitle("") frame:ShowCloseButton(false) frame:MakePopup() frame:SetBackgroundBlur(true) frame.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, C_BG_D) surface.SetDrawColor(C_GRAD) surface.DrawRect(0, 0, w, 3) draw.RoundedBoxEx(8, 0, 0, w, 50, C_BG_M, true, true, false, false) surface.SetDrawColor(C_BDR) surface.DrawLine(0, 50, w, 50) draw.SimpleText("ГАРДЕРОБ", "DermaLarge", w/2, 25, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) surface.SetDrawColor(C_ACC) surface.DrawRect(20, 48, 30, 2) surface.DrawRect(w-50, 48, 30, 2) surface.SetDrawColor(C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) end local closeBtn = vgui.Create("DButton", frame) closeBtn:SetPos(frame:GetWide() - 40, 10) closeBtn:SetSize(30, 30) closeBtn:SetText("") closeBtn.Paint = function(s, w, h) local col = s:IsHovered() and C_ACC_H or C_ACC draw.RoundedBox(4, 0, 0, w, h, col) draw.SimpleText("X", "DermaLarge", w/2, h/2-2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end closeBtn.DoClick = function() frame:Close() end local btnOffset = 45 local hintsPanel = vgui.Create("DPanel", frame) hintsPanel:SetPos(20, 60) hintsPanel.Expanded = true hintsPanel.CurHeight = 305 hintsPanel:SetSize(230, hintsPanel.CurHeight) hintsPanel.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, C_BG_M); surface.SetDrawColor(C_BDR); surface.DrawOutlinedRect(0, 0, w, h, 1) end hintsPanel.Think = function(s) local targetHeight = s.Expanded and 305 or 35 if s.CurHeight ~= targetHeight then s.CurHeight = Lerp(FrameTime() * 15, s.CurHeight, targetHeight); if math.abs(s.CurHeight - targetHeight) < 0.5 then s.CurHeight = targetHeight end; s:SetTall(math.Round(s.CurHeight)) end end local hintsHeader = vgui.Create("DButton", hintsPanel) hintsHeader:SetPos(0, 0); hintsHeader:SetSize(230, 35); hintsHeader:SetText("") hintsHeader.Paint = function(s, w, h) local col = s:IsHovered() and C_BG_L or C_BG_D draw.RoundedBoxEx(6, 0, 0, w, h, col, true, true, not hintsPanel.Expanded, not hintsPanel.Expanded) surface.SetDrawColor(C_ACC); surface.DrawRect(0, 0, 3, h) draw.SimpleText("УПРАВЛЕНИЕ", "DermaLarge", 10, h/2, C_TXT_P, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) draw.SimpleText(hintsPanel.Expanded and "▼" or "◀", "DermaDefault", w - 15, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end hintsHeader.DoClick = function(s) hintsPanel.Expanded = not hintsPanel.Expanded end local hintsContent = vgui.Create("DPanel", hintsPanel) hintsContent:SetPos(0, 35); hintsContent:SetSize(230, 270); hintsContent.Paint = function() end local binds = { {"ЛКМ / Перетаскивание", "Примерить одежду"}, {"ALT + ЛКМ", "Вращение камеры"}, {"SHIFT + ЛКМ", "Перемещение камеры"}, {"Колёсико мыши", "Приблизить / Отдалить"} } local hy = 10 for _, bind in ipairs(binds) do local bindItem = vgui.Create("DPanel", hintsContent) bindItem:SetPos(10, hy); bindItem:SetSize(210, 55) bindItem.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, C_BG_D); surface.SetDrawColor(C_BDR); surface.DrawOutlinedRect(0, 0, w, h, 1); draw.SimpleText(bind[1], "DermaDefaultBold", w/2, 16, C_WHITE, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER); draw.SimpleText(bind[2], "DermaDefault", w/2, 36, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end hy = hy + 60 end local presetsPanel = vgui.Create("DPanel", frame) presetsPanel.Expanded = false presetsPanel.CurHeight = 35 presetsPanel:SetSize(230, presetsPanel.CurHeight) local presetsContent = vgui.Create("DScrollPanel", presetsPanel) presetsContent:SetPos(0, 35) presetsPanel.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, C_BG_M) surface.SetDrawColor(C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) end presetsPanel.Think = function(s) local maxH = fH - hintsPanel.CurHeight - 80 local targetHeight = s.Expanded and maxH or 35 if s.CurHeight ~= targetHeight then s.CurHeight = Lerp(FrameTime() * 15, s.CurHeight, targetHeight) if math.abs(s.CurHeight - targetHeight) < 0.5 then s.CurHeight = targetHeight end s:SetTall(math.Round(s.CurHeight)) end local targetY = hintsPanel:GetY() + hintsPanel:GetTall() + 10 if s:GetY() ~= targetY then s:SetPos(20, targetY) end presetsContent:SetSize(230, s:GetTall() - 35) end local presetsHeader = vgui.Create("DButton", presetsPanel) presetsHeader:SetPos(0, 0); presetsHeader:SetSize(230, 35); presetsHeader:SetText("") presetsHeader.Paint = function(s, w, h) local col = s:IsHovered() and C_BG_L or C_BG_D draw.RoundedBoxEx(6, 0, 0, w, h, col, true, true, not presetsPanel.Expanded, not presetsPanel.Expanded) surface.SetDrawColor(C_ACC); surface.DrawRect(0, 0, 3, h); draw.SimpleText("СБОРКИ", "DermaLarge", 10, h/2, C_TXT_P, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) draw.SimpleText(presetsPanel.Expanded and "▼" or "◀", "DermaDefault", w - 15, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end presetsHeader.DoClick = function(s) presetsPanel.Expanded = not presetsPanel.Expanded end local allPresets = serverPresets or {} if not allPresets[model] then allPresets[model] = {} end local function SavePresetsToServer() net.Start("SandboxWardrobeSavePreset") net.WriteTable(allPresets) net.SendToServer() end -- НОРМАЛИЗАЦИЯ ключей (util.JSONToTable превращает строковые числа обратно в number type) local norm = {} for k, v in pairs(allPresets[model]) do norm[tostring(k)] = v end allPresets[model] = norm local py = 10 for i = 1, 10 do local slotPanel = vgui.Create("DPanel", presetsContent) slotPanel:SetPos(10, py) slotPanel:SetSize(200, 85) slotPanel.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, C_BG_D) surface.SetDrawColor(C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) end local pData = allPresets[model][i] or allPresets[model][tostring(i)] local hasData = pData ~= nil local defaultName = "Сборка " .. i local iconCell = vgui.Create("DPanel", slotPanel) iconCell:SetPos(130, 12) iconCell:SetSize(60, 60) iconCell.Paint = function(s, w, h) local curData = allPresets[model][tostring(i)] local isFilled = curData ~= nil draw.RoundedBox(4, 0, 0, w, h, isFilled and C_ARMA_SLOT or C_ARMA_SLOT_EMPTY) if isFilled then local mat = Material("wardrobe_ui/body.png", "noclamp smooth") if mat and not mat:IsError() then surface.SetDrawColor(255, 255, 255, 200) surface.SetMaterial(mat) surface.DrawTexturedRect(5, 5, w-10, h-10) end surface.SetDrawColor(C_PRI) surface.DrawOutlinedRect(0, 0, w, h, 1) else draw.SimpleText("ПУСТО", "DermaDefault", w/2, h/2, Color(100, 100, 100), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) surface.SetDrawColor(Color(0,0,0,100)) surface.DrawOutlinedRect(0, 0, w, h, 1) end end local nameEntry = vgui.Create("DTextEntry", slotPanel) nameEntry:SetPos(10, 12) nameEntry:SetSize(110, 25) nameEntry:SetText(hasData and (pData.name or defaultName) or defaultName) nameEntry.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, C_BG_M) surface.SetDrawColor(s:IsEditing() and C_PRI or C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) s:DrawTextEntryText(C_WHITE, C_PRI, C_WHITE) end nameEntry.OnChange = function(s) local curData = allPresets[model][tostring(i)] if curData then curData.name = s:GetText() timer.Create("WardrobePresetSave_"..i, 0.5, 1, function() if IsValid(frame) then SavePresetsToServer() end end) end end local btnW = 33 local btnY = 47 local loadBtn = vgui.Create("DButton", slotPanel) loadBtn:SetPos(10, btnY) loadBtn:SetSize(btnW, 25) loadBtn:SetText("") loadBtn.Paint = function(s, w, h) if not allPresets[model][tostring(i)] then draw.RoundedBox(4, 0, 0, w, h, Color(30, 30, 30, 150)) draw.SimpleText("ЗАГР", "DermaDefault", w/2, h/2, Color(100, 100, 100), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) return end local col = s:IsHovered() and C_ACC_H or C_ACC draw.RoundedBox(4, 0, 0, w, h, col) draw.SimpleText("ЗАГР", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end loadBtn.DoClick = function() local curData = allPresets[model][tostring(i)] if not curData then return end for idx, val in pairs(curData.bgs) do currentBodygroups[tonumber(idx) or idx] = val end if curData.skin then currentSkin = curData.skin end surface.PlaySound("buttons/button15.wav") end local saveBtn = vgui.Create("DButton", slotPanel) saveBtn:SetPos(48, btnY) saveBtn:SetSize(btnW, 25) saveBtn:SetText("") saveBtn.Paint = function(s, w, h) local col = s:IsHovered() and C_PRI_H or C_PRI draw.RoundedBox(4, 0, 0, w, h, col) draw.SimpleText("СОХР", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end saveBtn.DoClick = function() local finalName = nameEntry:GetText() if finalName == "" then finalName = defaultName end local safeBGs = {} for k, v in pairs(currentBodygroups) do safeBGs[tostring(k)] = v end allPresets[model][tostring(i)] = { name = finalName, bgs = safeBGs, skin = currentSkin } SavePresetsToServer() surface.PlaySound("buttons/button15.wav") end local delBtn = vgui.Create("DButton", slotPanel) delBtn:SetPos(87, btnY) delBtn:SetSize(btnW, 25) delBtn:SetText("") delBtn.Paint = function(s, w, h) if not allPresets[model][tostring(i)] then draw.RoundedBox(4, 0, 0, w, h, Color(30, 30, 30, 150)) draw.SimpleText("УДАЛ", "DermaDefault", w/2, h/2, Color(100, 100, 100), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) return end local col = s:IsHovered() and C_WARN_H or C_WARN draw.RoundedBox(4, 0, 0, w, h, col) draw.SimpleText("УДАЛ", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end delBtn.DoClick = function() if allPresets[model][tostring(i)] or allPresets[model][i] then allPresets[model][tostring(i)] = nil allPresets[model][i] = nil SavePresetsToServer() nameEntry:SetText(defaultName) surface.PlaySound("buttons/button15.wav") end end py = py + 95 end local modelPanel = vgui.Create("DModelPanel", frame) modelPanel:SetPos(midX, 60); modelPanel:SetSize(midW, fH - 140); modelPanel:SetModel(model) modelPanel:Receiver("wardrobe_item", function(pnl, tbl, bDoDrop, command, x, y) if bDoDrop then local droppedCell = tbl[1]; if IsValid(droppedCell) then if droppedCell.isBodygroup then currentBodygroups[droppedCell.bgIdx] = droppedCell.bgVal elseif droppedCell.isSkin then currentSkin = droppedCell.skinVal end; surface.PlaySound("UI/buttonclick.wav") end end end) local oldPaint = modelPanel.Paint modelPanel.Paint = function(s, w, h) if bgMat and not bgMat:IsError() then surface.SetDrawColor(255, 255, 255, 255); surface.SetMaterial(bgMat); surface.DrawTexturedRect(0, 0, w, h) end local mx, my = gui.MousePos(); local px, py = s:LocalToScreen(0, 0) local isHoveredDrag = dragndrop.IsDragging() and (mx >= px and mx <= px + w and my >= py and my <= py + h) if isHoveredDrag then surface.SetDrawColor(C_PRI); surface.DrawOutlinedRect(0, 0, w, h, 3); surface.SetDrawColor(C_GRAD); surface.DrawRect(0, 0, w, h) else surface.SetDrawColor(C_BDR); surface.DrawOutlinedRect(0, 0, w, h, 1) end oldPaint(s, w, h) end modelPanel.camAnims = { zoom = 60, rot = Angle(0, 180, 0), offset = Vector(0, 0, 0) }; modelPanel.curZoom = 60; modelPanel.curRot = Angle(0, 180, 0); modelPanel.curOffset = Vector(0, 0, 0) modelPanel.LayoutEntity = function(s, ent) if not s.bAnimSet then local seq = ent:LookupSequence("idle_subtle"); if seq <= 0 then seq = ent:LookupSequence("idle_all_01") end; if seq > 0 then ent:SetSequence(seq) end; s.bAnimSet = true end if (s.bDragging) then local x, y = gui.MousePos(); local dx = x - s.lastX; local dy = y - s.lastY if input.IsKeyDown(KEY_LSHIFT) or input.IsKeyDown(KEY_RSHIFT) then local right = s.curRot:Right(); local up = s.curRot:Up(); s.camAnims.offset = s.camAnims.offset - right * (dx * 0.15) + up * (dy * 0.15) elseif input.IsKeyDown(KEY_LALT) or input.IsKeyDown(KEY_RALT) then s.camAnims.rot.yaw = s.camAnims.rot.yaw - dx * 0.8; s.camAnims.rot.pitch = math.Clamp(s.camAnims.rot.pitch - dy * 0.8, -45, 45) end s.lastX, s.lastY = x, y end local speed = FrameTime() * 10; s.curZoom = Lerp(speed, s.curZoom, s.camAnims.zoom); s.curRot = LerpAngle(speed, s.curRot, s.camAnims.rot); s.curOffset = LerpVector(speed, s.curOffset, s.camAnims.offset) local targetPos = ent:GetPos() + Vector(0, 0, 40) + s.curOffset; s:SetCamPos(targetPos - s.curRot:Forward() * s.curZoom); s:SetLookAng(s.curRot) ent:FrameAdvance((RealTime() - (s.lastTick or RealTime())) * 1); s.lastTick = RealTime() end local function SetCameraAngle(angle) if angle == "front" then modelPanel.camAnims.rot = Angle(0, 180, 0) elseif angle == "side" then modelPanel.camAnims.rot = Angle(0, 90, 0) elseif angle == "back" then modelPanel.camAnims.rot = Angle(0, 0, 0) end modelPanel.camAnims.offset = Vector(0, 0, 0) end modelPanel.OnMousePressed = function(s, code) if code == MOUSE_LEFT then local clickTime = SysTime(); if s.lastClickTime and (clickTime - s.lastClickTime) < 0.3 then s.camAnims.zoom = 60; s.camAnims.rot = Angle(0, 180, 0); s.camAnims.offset = Vector(0, 0, 0); s.lastClickTime = 0; return end; s.lastClickTime = clickTime; s.bDragging = true; s.lastX, s.lastY = gui.MousePos() end end modelPanel.OnMouseReleased = function(s, code) if code == MOUSE_LEFT then s.bDragging = false end end modelPanel.OnCursorExited = function(s) s.bDragging = false end modelPanel.OnMouseWheeled = function(s, delta) s.camAnims.zoom = math.Clamp(s.camAnims.zoom - delta * 15, 10, 120) end timer.Simple(0.05, function() if IsValid(modelPanel) then SetCameraAngle("front") end end) modelPanel.Think = function(s) local entity = s:GetEntity(); if IsValid(entity) then for idx, value in pairs(currentBodygroups) do if entity:GetBodygroup(idx) ~= value then entity:SetBodygroup(idx, value) end end; if entity:GetSkin() ~= currentSkin then entity:SetSkin(currentSkin) end end end local viewButtons = vgui.Create("DPanel", frame) viewButtons:SetPos(midX, fH - 70); viewButtons:SetSize(midW, 50) viewButtons.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, C_BG_M); surface.SetDrawColor(C_BDR); surface.DrawOutlinedRect(0, 0, w, h, 1) end local angles = {{"СПЕРЕДИ", "front"}, {"СБОКУ", "side"}, {"СЗАДИ", "back"}} for k, v in ipairs(angles) do local btn = vgui.Create("DButton", viewButtons) btn.Paint = function(s, w, h) local col = s:IsHovered() and C_PRI_H or C_PRI; draw.RoundedBox(4, 0, 0, w, h, col); surface.SetDrawColor(C_BDR); surface.DrawOutlinedRect(0, 0, w, h, 1); draw.SimpleText(v[1], "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end btn.DoClick = function() SetCameraAngle(v[2]) end btn.PerformLayout = function(s, w, h) local bs = (k == 2) and 20 or 10 local bw = (viewButtons:GetWide() - 40) / 3 btn:SetSize(bw, 30) btn:SetPos(10 + (k-1)*(bw+10), 10) end btn:SetText("") end local HoveredItemName = nil local HoveredItemDesc = nil local tooltipPanel = vgui.Create("DPanel", frame) tooltipPanel:SetSize(280, 100) tooltipPanel:SetDrawOnTop(true) tooltipPanel:SetMouseInputEnabled(false) tooltipPanel:SetAlpha(0) tooltipPanel.Paint = function(s, w, h) draw.RoundedBox(0, 0, 0, w, h, C_ARMA_TOOLTIP_BG) surface.SetDrawColor(C_ARMA_SEL) surface.DrawRect(0, 0, w, 3) if HoveredItemName then draw.SimpleText(HoveredItemName, "DermaDefaultBold", 10, 12, C_ARMA_SEL, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) end if HoveredItemDesc then draw.DrawText(HoveredItemDesc, "DermaDefault", 10, 35, C_ARMA_TEXT, TEXT_ALIGN_LEFT) end end tooltipPanel.Think = function(s) if HoveredItemName then s:SetAlpha(math.Clamp(s:GetAlpha() + FrameTime() * 1500, 0, 255)) local mx, my = gui.MousePos() local fx, fy = frame:GetPos() s:SetPos(mx - fx + 15, my - fy + 15) local _, lineCount = string.gsub(HoveredItemDesc or "", "\n", "") s:SetTall(50 + (lineCount * 16)) else s:SetAlpha(math.Clamp(s:GetAlpha() - FrameTime() * 1500, 0, 255)) end end local settingsPanel = vgui.Create("DPanel", frame) settingsPanel:SetPos(rightX, 60) settingsPanel:SetSize(rightW, fH - 80) settingsPanel.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, C_BG_M) surface.SetDrawColor(C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) end local settingsScroll = vgui.Create("DScrollPanel", settingsPanel) settingsScroll:Dock(FILL) settingsScroll:DockMargin(10, 10, 10, 55) local sbar = settingsScroll:GetVBar() sbar:SetWide(4) sbar.Paint = function() end sbar.btnGrip.Paint = function(s, w, h) draw.RoundedBox(0, 0, 0, w, h, Color(100, 100, 100, 100)) end sbar.btnUp.Paint = function() end sbar.btnDown.Paint = function() end local baseCellSize = 108 local bgMainHeaderLabel = vgui.Create("DLabel", settingsScroll) bgMainHeaderLabel:Dock(TOP) bgMainHeaderLabel:DockMargin(22, 0, 30, 15) bgMainHeaderLabel:SetTall(20) bgMainHeaderLabel:SetText("РЕДАКТИРОВАНИЕ БОДИГРУПП") bgMainHeaderLabel:SetFont("DermaDefaultBold") bgMainHeaderLabel:SetTextColor(C_ARMA_SEL) if table.Count(bodygroups) > 0 then local bgArray = {} for idx, data in pairs(bodygroups) do table.insert(bgArray, data) end table.sort(bgArray, function(a, b) return a.index < b.index end) for _, data in ipairs(bgArray) do local idx = data.index local bgName = string.lower(data.name) local allowedValues = {} local blockedForModel = WARDROBE_BLOCKED_BGS[string.lower(model)] or {} for i = 0, data.count - 1 do local isBlocked = false if not (unlockedBodygroups[tostring(idx)] and unlockedBodygroups[tostring(idx)][tostring(i)]) then for _, blockData in ipairs(blockedForModel) do if blockData[1] == idx and blockData[2] == i then if isfunction(blockData[3]) then if not blockData[3](LocalPlayer()) then isBlocked = true end else isBlocked = true end break end end end if not isBlocked then table.insert(allowedValues, i) end end if #allowedValues == 0 then continue end local bgPanel = vgui.Create("DPanel", settingsScroll) bgPanel:Dock(TOP) bgPanel:DockMargin(10, 5, 30, 10) bgPanel.Expanded = true local panelExpandedHeight = 25 + baseCellSize bgPanel.CurHeight = panelExpandedHeight bgPanel:SetTall(panelExpandedHeight) bgPanel.Paint = function(s, w, h) end bgPanel.Think = function(s) local targetHeight = s.Expanded and panelExpandedHeight or 25 if s.CurHeight ~= targetHeight then s.CurHeight = Lerp(FrameTime() * 15, s.CurHeight, targetHeight) if math.abs(s.CurHeight - targetHeight) < 0.5 then s.CurHeight = targetHeight end s:SetTall(math.Round(s.CurHeight)) end end local headerBtn = vgui.Create("DButton", bgPanel) headerBtn:SetPos(22, 0) headerBtn:SetSize(336, 25) headerBtn:SetText("") headerBtn.Paint = function(s, w, h) local col = s:IsHovered() and Color(255, 255, 255) or Color(200, 200, 200) draw.SimpleText(string.upper(data.name), "DermaDefaultBold", 0, h/2, col, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) draw.SimpleText(bgPanel.Expanded and "▼" or "◀", "DermaDefault", w, h/2, col, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) end headerBtn.DoClick = function(s) bgPanel.Expanded = not bgPanel.Expanded surface.PlaySound("UI/buttonclick.wav") end local carouselPanel = vgui.Create("DPanel", bgPanel) carouselPanel:SetPos(22, 25) carouselPanel:SetSize(336, baseCellSize) carouselPanel.Paint = function() end local startScrollIdx = 0 for k, v in ipairs(allowedValues) do if v == (currentBodygroups[idx] or 0) then startScrollIdx = k - 1 break end end carouselPanel.TargetScroll = startScrollIdx carouselPanel.CurScroll = carouselPanel.TargetScroll local leftBtn = vgui.Create("DButton", bgPanel) leftBtn:SetPos(0, 25) leftBtn:SetSize(16, baseCellSize) leftBtn:SetText("") leftBtn.Paint = function(s, w, h) local col = s:IsHovered() and C_ARMA_SLOT_HOVER or C_ARMA_SLOT_EMPTY draw.RoundedBox(0, 0, 0, w, h, col) draw.SimpleText("<", "DermaDefaultBold", w/2, h/2, s:IsHovered() and C_ARMA_SEL or Color(150, 150, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) surface.SetDrawColor(Color(0, 0, 0, 120)) surface.DrawOutlinedRect(0, 0, w, h, 1) end local rightBtn = vgui.Create("DButton", bgPanel) rightBtn:SetPos(364, 25) rightBtn:SetSize(16, baseCellSize) rightBtn:SetText("") rightBtn.Paint = function(s, w, h) local col = s:IsHovered() and C_ARMA_SLOT_HOVER or C_ARMA_SLOT_EMPTY draw.RoundedBox(0, 0, 0, w, h, col) draw.SimpleText(">", "DermaDefaultBold", w/2, h/2, s:IsHovered() and C_ARMA_SEL or Color(150, 150, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) surface.SetDrawColor(Color(0, 0, 0, 120)) surface.DrawOutlinedRect(0, 0, w, h, 1) end local maxScroll = math.max(0, #allowedValues - 1) leftBtn.DoClick = function() carouselPanel.TargetScroll = math.max(0, carouselPanel.TargetScroll - 1) surface.PlaySound("UI/buttonclick.wav") end rightBtn.DoClick = function() carouselPanel.TargetScroll = math.min(maxScroll, carouselPanel.TargetScroll + 1) surface.PlaySound("UI/buttonclick.wav") end local cells = {} for scrollIdx, val in ipairs(allowedValues) do local i = val local cell = vgui.Create("DButton", carouselPanel) cell:SetText("") cell.isBodygroup = true cell.bgIdx = idx cell.bgVal = i cell:Droppable("wardrobe_item") cell.OnCursorEntered = function(s) HoveredItemName = CUSTOM_NAMES_BG[bgName] and CUSTOM_NAMES_BG[bgName][i] or (string.upper(bgName) .. " #" .. i) HoveredItemDesc = CUSTOM_DESC_BG[bgName] and CUSTOM_DESC_BG[bgName][i] or "Дополнительная экипировка.\n\n0.5kg" end cell.OnCursorExited = function(s) HoveredItemName = nil; HoveredItemDesc = nil end cell.DoClick = function(s) currentBodygroups[idx] = i carouselPanel.TargetScroll = scrollIdx - 1 surface.PlaySound("UI/buttonclick.wav") end cell.Paint = function(s, w, h) local isSelected = (currentBodygroups[idx] or 0) == i local bgColor = C_ARMA_SLOT if isSelected then bgColor = C_ARMA_SEL elseif s:IsHovered() then bgColor = C_ARMA_SLOT_HOVER end local alphaMult = math.Clamp(1 - (s.AbsOffset or 0) * 0.35, 0.2, 1) if s:IsDragging() then surface.SetDrawColor(255, 255, 255, 20); surface.DrawRect(0, 0, w, h); return end surface.SetDrawColor(bgColor.r, bgColor.g, bgColor.b, bgColor.a * alphaMult) surface.DrawRect(0, 0, w, h) local iconData = CUSTOM_ICONS_BG[bgName] local customIcon = iconData and (iconData[i] or iconData.default) if customIcon and not customIcon:IsError() then surface.SetDrawColor(255, 255, 255, 255 * alphaMult) surface.SetMaterial(customIcon) surface.DrawTexturedRect(4, 4, w - 8, h - 8) else local txtColor = isSelected and Color(40, 40, 40, 255 * alphaMult) or Color(150, 150, 150, 255 * alphaMult) draw.SimpleText(tostring(i), "DermaLarge", w/2, h/2, txtColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end surface.SetDrawColor(0, 0, 0, 120 * alphaMult) surface.DrawOutlinedRect(0, 0, w, h, 1) end table.insert(cells, cell) end carouselPanel.Think = function(s) s.CurScroll = Lerp(FrameTime() * 12, s.CurScroll, s.TargetScroll) for i, cell in ipairs(cells) do local index = i - 1 local offset = index - s.CurScroll local absOffset = math.abs(offset) cell.AbsOffset = absOffset local scale = math.Clamp(1 - absOffset * 0.25, 0.5, 1) local size = baseCellSize * scale local spacing = 65 local x = (s:GetWide() / 2) - (size / 2) + (offset * spacing) local y = (s:GetTall() / 2) - (size / 2) cell:SetSize(size, size) cell:SetPos(x, y) cell:SetZPos(100 - math.floor(absOffset * 10)) end end end end if #availableSkins > 1 then local skinPanel = vgui.Create("DPanel", settingsScroll) skinPanel:Dock(TOP) skinPanel:DockMargin(10, 5, 30, 10) skinPanel.Expanded = true local panelExpandedHeight = 25 + baseCellSize skinPanel.CurHeight = panelExpandedHeight skinPanel:SetTall(panelExpandedHeight) skinPanel.Paint = function(s, w, h) end skinPanel.Think = function(s) local targetHeight = s.Expanded and panelExpandedHeight or 25 if s.CurHeight ~= targetHeight then s.CurHeight = Lerp(FrameTime() * 15, s.CurHeight, targetHeight) if math.abs(s.CurHeight - targetHeight) < 0.5 then s.CurHeight = targetHeight end s:SetTall(math.Round(s.CurHeight)) end end local headerBtn = vgui.Create("DButton", skinPanel) headerBtn:SetPos(22, 0) headerBtn:SetSize(336, 25) headerBtn:SetText("") headerBtn.Paint = function(s, w, h) local col = s:IsHovered() and Color(255, 255, 255) or Color(200, 200, 200) draw.SimpleText("ВАРИАНТ КАМУФЛЯЖА", "DermaDefaultBold", 0, h/2, col, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) draw.SimpleText(skinPanel.Expanded and "▼" or "◀", "DermaDefault", w, h/2, col, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) end headerBtn.DoClick = function(s) skinPanel.Expanded = not skinPanel.Expanded surface.PlaySound("UI/buttonclick.wav") end local carouselPanel = vgui.Create("DPanel", skinPanel) carouselPanel:SetPos(22, 25) carouselPanel:SetSize(336, baseCellSize) carouselPanel.Paint = function() end carouselPanel.TargetScroll = currentSkin or 0 carouselPanel.CurScroll = carouselPanel.TargetScroll local leftBtn = vgui.Create("DButton", skinPanel) leftBtn:SetPos(0, 25); leftBtn:SetSize(16, baseCellSize); leftBtn:SetText("") leftBtn.Paint = function(s, w, h) local col = s:IsHovered() and C_ARMA_SLOT_HOVER or C_ARMA_SLOT_EMPTY draw.RoundedBox(0, 0, 0, w, h, col) draw.SimpleText("<", "DermaDefaultBold", w/2, h/2, s:IsHovered() and C_ARMA_SEL or Color(150, 150, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) surface.SetDrawColor(Color(0, 0, 0, 120)); surface.DrawOutlinedRect(0, 0, w, h, 1) end local rightBtn = vgui.Create("DButton", skinPanel) rightBtn:SetPos(364, 25); rightBtn:SetSize(16, baseCellSize); rightBtn:SetText("") rightBtn.Paint = function(s, w, h) local col = s:IsHovered() and C_ARMA_SLOT_HOVER or C_ARMA_SLOT_EMPTY draw.RoundedBox(0, 0, 0, w, h, col) draw.SimpleText(">", "DermaDefaultBold", w/2, h/2, s:IsHovered() and C_ARMA_SEL or Color(150, 150, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) surface.SetDrawColor(Color(0, 0, 0, 120)); surface.DrawOutlinedRect(0, 0, w, h, 1) end local maxScroll = math.max(0, #availableSkins - 1) leftBtn.DoClick = function() carouselPanel.TargetScroll = math.max(0, carouselPanel.TargetScroll - 1) surface.PlaySound("UI/buttonclick.wav") end rightBtn.DoClick = function() carouselPanel.TargetScroll = math.min(maxScroll, carouselPanel.TargetScroll + 1) surface.PlaySound("UI/buttonclick.wav") end local cells = {} for i = 0, #availableSkins - 1 do local cell = vgui.Create("DButton", carouselPanel) cell:SetText("") cell.isSkin = true cell.skinVal = i cell:Droppable("wardrobe_item") cell.OnCursorEntered = function(s) HoveredItemName = CUSTOM_NAMES_SKIN[i] or ("Текстура #" .. i); HoveredItemDesc = CUSTOM_DESC_SKIN[i] or "Альтернативный паттерн экипировки." end cell.OnCursorExited = function(s) HoveredItemName = nil; HoveredItemDesc = nil end cell.DoClick = function(s) currentSkin = i carouselPanel.TargetScroll = i surface.PlaySound("UI/buttonclick.wav") end cell.Paint = function(s, w, h) local isSelected = currentSkin == i local bgColor = C_ARMA_SLOT if isSelected then bgColor = C_ARMA_SEL elseif s:IsHovered() then bgColor = C_ARMA_SLOT_HOVER end local alphaMult = math.Clamp(1 - (s.AbsOffset or 0) * 0.35, 0.2, 1) if s:IsDragging() then surface.SetDrawColor(255, 255, 255, 20); surface.DrawRect(0, 0, w, h); return end surface.SetDrawColor(bgColor.r, bgColor.g, bgColor.b, bgColor.a * alphaMult) surface.DrawRect(0, 0, w, h) local customIcon = CUSTOM_ICONS_SKIN[i] or CUSTOM_ICONS_SKIN.default if customIcon and not customIcon:IsError() then surface.SetDrawColor(255, 255, 255, 255 * alphaMult) surface.SetMaterial(customIcon) surface.DrawTexturedRect(4, 4, w - 8, h - 8) else local txtColor = isSelected and Color(40, 40, 40, 255 * alphaMult) or Color(150, 150, 150, 255 * alphaMult) draw.SimpleText(tostring(i), "DermaLarge", w/2, h/2, txtColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end surface.SetDrawColor(0, 0, 0, 120 * alphaMult) surface.DrawOutlinedRect(0, 0, w, h, 1) end table.insert(cells, cell) end carouselPanel.Think = function(s) s.CurScroll = Lerp(FrameTime() * 12, s.CurScroll, s.TargetScroll) for i, cell in ipairs(cells) do local index = i - 1 local offset = index - s.CurScroll local absOffset = math.abs(offset) cell.AbsOffset = absOffset local scale = math.Clamp(1 - absOffset * 0.25, 0.5, 1) local size = baseCellSize * scale local spacing = 65 local x = (s:GetWide() / 2) - (size / 2) + (offset * spacing) local y = (s:GetTall() / 2) - (size / 2) cell:SetSize(size, size) cell:SetPos(x, y) cell:SetZPos(100 - math.floor(absOffset * 10)) end end end local applyBtn = vgui.Create("DButton", settingsPanel) local wipeBtn = vgui.Create("DButton", settingsPanel) wipeBtn:SetSize(135, 35); wipeBtn:SetText("") wipeBtn.Paint = function(s, w, h) local col = s:IsHovered() and C_WARN_H or C_WARN draw.RoundedBox(8, 0, 0, w, h, C_BDR); draw.RoundedBox(8, 2, 2, w-4, h-4, col) draw.SimpleText("СБРОСИТЬ", "DermaDefaultBold", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end wipeBtn.DoClick = function() net.Start("SandboxWardrobeApply"); net.WriteBool(true); net.SendToServer(); frame:Close() end applyBtn:SetSize(135, 35); applyBtn:SetText("") applyBtn.Paint = function(s, w, h) local col = s:IsHovered() and C_PRI_H or C_PRI draw.RoundedBox(8, 0, 0, w, h, C_BDR); draw.RoundedBox(8, 2, 2, w-4, h-4, col); draw.RoundedBoxEx(8, 2, 2, w-4, h/2-2, C_GRAD, true, true, false, false) draw.SimpleText("ПРИМЕНИТЬ", "DermaDefaultBold", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end applyBtn.DoClick = function() net.Start("SandboxWardrobeApply"); net.WriteBool(false); net.WriteTable(currentBodygroups); net.WriteUInt(currentSkin, 8); net.SendToServer(); frame:Close() end settingsPanel.PerformLayout = function(s, w, h) applyBtn:SetPos(w - 135 - 10, h - 35 - 10) wipeBtn:SetPos(w - 135*2 - 20, h - 35 - 10) end end) -- ===================================================================== -- АДМИН ПАНЕЛЬ -- ===================================================================== net.Receive("SandboxWardrobeAdminOpen", function() local unlockedData = net.ReadTable() local fW, fH = 800, 600 local frame = vgui.Create("DFrame") frame:SetSize(fW, fH) frame:Center() frame:SetTitle("") frame:ShowCloseButton(false) frame:MakePopup() frame:SetBackgroundBlur(true) frame.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, C_BG_D) surface.SetDrawColor(C_GRAD) surface.DrawRect(0, 0, w, 3) draw.RoundedBoxEx(8, 0, 0, w, 50, C_BG_M, true, true, false, false) surface.SetDrawColor(C_BDR) surface.DrawLine(0, 50, w, 50) draw.SimpleText("АДМИН ПАНЕЛЬ (ДОСТУП)", "DermaLarge", w/2, 25, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) surface.SetDrawColor(C_ACC) surface.DrawRect(20, 48, 30, 2) surface.DrawRect(w-50, 48, 30, 2) surface.SetDrawColor(C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) end local closeBtn = vgui.Create("DButton", frame) closeBtn:SetPos(frame:GetWide() - 40, 10) closeBtn:SetSize(30, 30) closeBtn:SetText("") closeBtn.Paint = function(s, w, h) local col = s:IsHovered() and C_ACC_H or C_ACC draw.RoundedBox(4, 0, 0, w, h, col) draw.SimpleText("X", "DermaLarge", w/2, h/2-2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end closeBtn.DoClick = function() frame:Close() end local leftPanel = vgui.Create("DPanel", frame) leftPanel:SetPos(20, 60) leftPanel:SetSize(230, fH - 80) leftPanel.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, C_BG_M) surface.SetDrawColor(C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) end local plyList = vgui.Create("DListView", leftPanel) plyList:Dock(FILL) plyList:DockMargin(10, 10, 10, 10) plyList:AddColumn("Игрок") plyList.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, C_BG_D) surface.SetDrawColor(C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) end local function FormatLine(line) for _, col in pairs(line.Columns) do col:SetTextColor(C_WHITE) col:SetFont("DermaDefaultBold") end line.Paint = function(s, w, h) local col = C_BG_M if s:IsSelected() then col = C_PRI elseif s:IsHovered() then col = C_BG_L end draw.RoundedBox(0, 0, 0, w, h, col) surface.SetDrawColor(C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) end end local manualBtn = vgui.Create("DButton", leftPanel) manualBtn:Dock(BOTTOM) manualBtn:DockMargin(10, 0, 10, 10) manualBtn:SetTall(30) manualBtn:SetText("") manualBtn.Paint = function(s, w, h) local col = s:IsHovered() and C_PRI_H or C_PRI draw.RoundedBox(4, 0, 0, w, h, col) surface.SetDrawColor(C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) draw.SimpleText("ДОБАВИТЬ ID", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end manualBtn.DoClick = function() Derma_StringRequest("Оффлайн игрок", "SteamID64:", "", function(text) if string.len(text) == 17 then local line = plyList:AddLine("Оффлайн ("..text..")") FormatLine(line) line.sid = text line.mdl = "models/cwz/characters/mason_pm.mdl" end end) end local selectedSid = nil local rightPanel = vgui.Create("DPanel", frame) rightPanel:SetPos(260, 60) rightPanel:SetSize(fW - 280, fH - 80) rightPanel.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, C_BG_M) surface.SetDrawColor(C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) end local topRow = vgui.Create("DPanel", rightPanel) topRow:Dock(TOP) topRow:SetTall(45) topRow:DockMargin(10, 10, 10, 0) topRow.Paint = function() end local modelEntry = vgui.Create("DTextEntry", topRow) modelEntry:Dock(FILL) modelEntry:DockMargin(0, 0, 10, 15) modelEntry:SetPlaceholderText(" Путь к модели (напр. models/cwz/characters/mason_pm.mdl)") modelEntry.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, C_BG_D) surface.SetDrawColor(C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) s:DrawTextEntryText(C_WHITE, C_ACC, C_WHITE) end local loadBtn = vgui.Create("DButton", topRow) loadBtn:Dock(RIGHT) loadBtn:SetWide(100) loadBtn:DockMargin(0, 0, 0, 15) loadBtn:SetText("") loadBtn.Paint = function(s, w, h) local col = s:IsHovered() and C_PRI_H or C_PRI draw.RoundedBox(4, 0, 0, w, h, col) surface.SetDrawColor(C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) draw.SimpleText("ОБНОВИТЬ", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end local bgScroll = vgui.Create("DScrollPanel", rightPanel) bgScroll:Dock(FILL) bgScroll:DockMargin(10, 0, 10, 10) local sbar = bgScroll:GetVBar() sbar:SetWide(4) sbar.Paint = function() end sbar.btnGrip.Paint = function(s, w, h) draw.RoundedBox(0, 0, 0, w, h, Color(100, 100, 100, 100)) end sbar.btnUp.Paint = function() end sbar.btnDown.Paint = function() end local function LoadModelBGs(modelSearch) bgScroll:Clear() if not modelSearch or modelSearch == "" then return end local sidData = unlockedData[selectedSid] or {} local modelData = sidData[string.lower(modelSearch)] or {} local blockedForModel = WARDROBE_BLOCKED_BGS[string.lower(modelSearch)] if not blockedForModel or table.IsEmpty(blockedForModel) then local err = bgScroll:Add("DLabel") err:SetText(" Для этой модели нет заблокированных бодигрупп в настройках.") err:SetTextColor(C_WARN) err:SetFont("DermaDefaultBold") err:SizeToContents() err:Dock(TOP) return end for _, blockData in ipairs(blockedForModel) do local bg_id = blockData[1] local bg_val = blockData[2] local str_bg_id = tostring(bg_id) local str_bg_val = tostring(bg_val) local bg_name = blockData[4] or ("Бодигруппа " .. bg_id .. " Вариант " .. bg_val) local linePnl = vgui.Create("DPanel", bgScroll) linePnl:Dock(TOP) linePnl:SetTall(35) linePnl:DockMargin(0, 5, 10, 5) linePnl.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, C_BG_D) surface.SetDrawColor(C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) end local lbl = vgui.Create("DLabel", linePnl) lbl:Dock(LEFT) lbl:DockMargin(10, 0, 0, 0) lbl:SetWide(300) lbl:SetText(bg_name) lbl:SetFont("DermaDefaultBold") lbl:SetTextColor(C_WHITE) local lockBtn = vgui.Create("DButton", linePnl) lockBtn:Dock(RIGHT) lockBtn:SetWide(100) lockBtn:DockMargin(0, 5, 5, 5) lockBtn:SetText("") local isUnlocked = (modelData[str_bg_id] and modelData[str_bg_id][str_bg_val]) and true or false lockBtn.IsUnlocked = isUnlocked lockBtn.Paint = function(s, w, h) local col = s.IsUnlocked and C_PRI or C_WARN if s:IsHovered() then col = s.IsUnlocked and C_PRI_H or C_WARN_H end draw.RoundedBox(4, 0, 0, w, h, col) draw.SimpleText(s.IsUnlocked and "ОТКРЫТ" or "ЗАКРЫТ", "DermaDefaultBold", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end lockBtn.DoClick = function(s) s.IsUnlocked = not s.IsUnlocked unlockedData[selectedSid] = unlockedData[selectedSid] or {} unlockedData[selectedSid][string.lower(modelSearch)] = unlockedData[selectedSid][string.lower(modelSearch)] or {} unlockedData[selectedSid][string.lower(modelSearch)][str_bg_id] = unlockedData[selectedSid][string.lower(modelSearch)][str_bg_id] or {} unlockedData[selectedSid][string.lower(modelSearch)][str_bg_id][str_bg_val] = s.IsUnlocked and true or nil net.Start("SandboxWardrobeAdminUpdate") net.WriteString(selectedSid) net.WriteString(modelSearch) net.WriteUInt(bg_id, 8) net.WriteUInt(bg_val, 8) net.WriteBool(s.IsUnlocked) net.SendToServer() surface.PlaySound("UI/buttonclick.wav") end end end for _, ply in ipairs(player.GetAll()) do local line = plyList:AddLine(ply:Nick()) FormatLine(line) line.sid = ply:SteamID64() line.mdl = ply:GetModel() end plyList.OnRowSelected = function(lst, index, pnl) selectedSid = pnl.sid modelEntry:SetText(pnl.mdl or "") LoadModelBGs(pnl.mdl) end loadBtn.DoClick = function() if not selectedSid then return end LoadModelBGs(modelEntry:GetText()) end end) -- ===================================================================== -- ПАНЕЛЬ КОМАНДИРА -- ===================================================================== local C_CMD = Color(40, 75, 120) local C_CMD_H = Color(50, 90, 140) local C_CMD_SEL = Color(70, 130, 180) net.Receive("SandboxWardrobeCommanderOpen", function() local membersData = net.ReadTable() local scrW, scrH = ScrW(), ScrH() local fW = math.Clamp(scrW * 0.65, 1000, 1600) local fH = math.Clamp(scrH * 0.65, 650, 1000) local frame = vgui.Create("DFrame") frame:SetSize(fW, fH) frame:Center() frame:SetTitle("") frame:ShowCloseButton(false) frame:MakePopup() frame:SetBackgroundBlur(true) frame.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, C_BG_D) surface.SetDrawColor(C_GRAD) surface.DrawRect(0, 0, w, 3) draw.RoundedBoxEx(8, 0, 0, w, 50, C_BG_M, true, true, false, false) surface.SetDrawColor(C_BDR) surface.DrawLine(0, 50, w, 50) draw.SimpleText("ПАНЕЛЬ КОМАНДИРА", "DermaLarge", w/2, 25, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) surface.SetDrawColor(C_CMD) surface.DrawRect(20, 48, 30, 2) surface.DrawRect(w - 50, 48, 30, 2) surface.SetDrawColor(C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) end local closeBtn = vgui.Create("DButton", frame) closeBtn:SetPos(frame:GetWide() - 40, 10) closeBtn:SetSize(30, 30) closeBtn:SetText("") closeBtn.Paint = function(s, w, h) local col = s:IsHovered() and C_ACC_H or C_ACC draw.RoundedBox(4, 0, 0, w, h, col) draw.SimpleText("X", "DermaLarge", w/2, h/2 - 2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end closeBtn.DoClick = function() frame:Close() end -- Предварительное объявление локальных функций для перестроения интерфейса local selectedMember = nil local cmdCurrentBodygroups = {} local cmdCurrentSkin = 0 local cmdModel = "" local cmdBodygroups = {} local cmdAvailableSkins = {} local RebuildSettings local RebuildCommanderPresets -- === ЛЕВАЯ ПАНЕЛЬ: СПИСОК ПОДРАЗДЕЛЕНИЯ И СБОРКИ === local leftW = 230 local leftContainer = vgui.Create("DPanel", frame) leftContainer:SetPos(20, 60) leftContainer:SetSize(leftW, fH - 80) leftContainer.Paint = function() end local squadPanel = vgui.Create("DPanel", leftContainer) squadPanel:Dock(FILL) squadPanel.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, C_BG_M) surface.SetDrawColor(C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) end local squadLabel = vgui.Create("DLabel", squadPanel) squadLabel:Dock(TOP) squadLabel:DockMargin(10, 10, 10, 5) squadLabel:SetTall(20) squadLabel:SetText("ПОДРАЗДЕЛЕНИЕ") squadLabel:SetFont("DermaDefaultBold") squadLabel:SetTextColor(C_CMD_SEL) local memberList = vgui.Create("DListView", squadPanel) memberList:Dock(FILL) memberList:DockMargin(10, 5, 10, 10) memberList:AddColumn("Боец") memberList.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, C_BG_D) surface.SetDrawColor(C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) end local function FormatMemberLine(line) for _, col in pairs(line.Columns) do col:SetTextColor(C_WHITE) col:SetFont("DermaDefaultBold") end line.Paint = function(s, w, h) local col = C_BG_M if s:IsSelected() then col = C_CMD elseif s:IsHovered() then col = C_BG_L end draw.RoundedBox(0, 0, 0, w, h, col) surface.SetDrawColor(C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) end end if #membersData == 0 then local emptyLbl = vgui.Create("DLabel", squadPanel) emptyLbl:Dock(TOP) emptyLbl:DockMargin(10, 10, 10, 0) emptyLbl:SetTall(40) emptyLbl:SetText("Нет бойцов\nв подразделении") emptyLbl:SetFont("DermaDefault") emptyLbl:SetTextColor(Color(120, 120, 120)) emptyLbl:SetWrap(true) end for _, data in ipairs(membersData) do local line = memberList:AddLine(data.nick) FormatMemberLine(line) line.memberData = data end -- === ПАНЕЛЬ СБОРОК КОМАНДИРА === local presetsPanel = vgui.Create("DPanel", leftContainer) presetsPanel:Dock(BOTTOM) presetsPanel:DockMargin(0, 10, 0, 0) presetsPanel.Expanded = false presetsPanel.CurHeight = 35 presetsPanel:SetTall(35) presetsPanel.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, C_BG_M) surface.SetDrawColor(C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) end presetsPanel.Think = function(s) local maxH = fH - 80 - 150 -- Резервируем хотя бы 150px для списка бойцов local targetHeight = s.Expanded and maxH or 35 if s.CurHeight ~= targetHeight then s.CurHeight = Lerp(FrameTime() * 15, s.CurHeight, targetHeight) if math.abs(s.CurHeight - targetHeight) < 0.5 then s.CurHeight = targetHeight end s:SetTall(math.Round(s.CurHeight)) end end local presetsHeader = vgui.Create("DButton", presetsPanel) presetsHeader:Dock(TOP) presetsHeader:SetTall(35) presetsHeader:SetText("") presetsHeader.Paint = function(s, w, h) local col = s:IsHovered() and C_BG_L or C_BG_D draw.RoundedBoxEx(6, 0, 0, w, h, col, true, true, not presetsPanel.Expanded, not presetsPanel.Expanded) surface.SetDrawColor(C_CMD) surface.DrawRect(0, 0, 3, h) draw.SimpleText("СБОРКИ", "DermaLarge", 10, h/2, C_TXT_P, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) draw.SimpleText(presetsPanel.Expanded and "▼" or "◀", "DermaDefault", w - 15, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end presetsHeader.DoClick = function(s) presetsPanel.Expanded = not presetsPanel.Expanded; surface.PlaySound("UI/buttonclick.wav") end local presetsContent = vgui.Create("DScrollPanel", presetsPanel) presetsContent:Dock(FILL) RebuildCommanderPresets = function() presetsContent:Clear() if not selectedMember or cmdModel == "" then return end local allPresets = {} if file.Exists("wardrobe_presets.txt", "DATA") then allPresets = util.JSONToTable(file.Read("wardrobe_presets.txt", "DATA")) or {} end if not allPresets[cmdModel] then allPresets[cmdModel] = {} end local py = 10 for i = 1, 10 do local slotPanel = vgui.Create("DPanel", presetsContent) slotPanel:SetPos(10, py) slotPanel:SetSize(200, 85) slotPanel.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, C_BG_D) surface.SetDrawColor(C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) end local pData = allPresets[cmdModel][i] or allPresets[cmdModel][tostring(i)] local hasData = pData ~= nil local defaultName = "Сборка " .. i local iconCell = vgui.Create("DPanel", slotPanel) iconCell:SetPos(130, 12) iconCell:SetSize(60, 60) iconCell.Paint = function(s, w, h) local curData = allPresets[cmdModel][tostring(i)] local isFilled = curData ~= nil draw.RoundedBox(4, 0, 0, w, h, isFilled and C_ARMA_SLOT or C_ARMA_SLOT_EMPTY) if isFilled then local mat = Material("wardrobe_ui/body.png", "noclamp smooth") if mat and not mat:IsError() then surface.SetDrawColor(255, 255, 255, 200) surface.SetMaterial(mat) surface.DrawTexturedRect(5, 5, w-10, h-10) end surface.SetDrawColor(C_CMD) surface.DrawOutlinedRect(0, 0, w, h, 1) else draw.SimpleText("ПУСТО", "DermaDefault", w/2, h/2, Color(100, 100, 100), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) surface.SetDrawColor(Color(0,0,0,100)) surface.DrawOutlinedRect(0, 0, w, h, 1) end end local nameEntry = vgui.Create("DTextEntry", slotPanel) nameEntry:SetPos(10, 12) nameEntry:SetSize(110, 25) nameEntry:SetText(hasData and (pData.name or defaultName) or defaultName) nameEntry.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, C_BG_M) surface.SetDrawColor(s:IsEditing() and C_CMD or C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) s:DrawTextEntryText(C_WHITE, C_CMD, C_WHITE) end nameEntry.OnChange = function(s) local curData = allPresets[cmdModel][tostring(i)] if curData then curData.name = s:GetText() file.Write("wardrobe_presets.txt", util.TableToJSON(allPresets)) end end local btnW = 33 local btnY = 47 local loadBtn = vgui.Create("DButton", slotPanel) loadBtn:SetPos(10, btnY) loadBtn:SetSize(btnW, 25) loadBtn:SetText("") loadBtn.Paint = function(s, w, h) if not allPresets[cmdModel][tostring(i)] then draw.RoundedBox(4, 0, 0, w, h, Color(30, 30, 30, 150)) draw.SimpleText("ЗАГР", "DermaDefault", w/2, h/2, Color(100, 100, 100), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) return end local col = s:IsHovered() and C_CMD_H or C_CMD draw.RoundedBox(4, 0, 0, w, h, col) draw.SimpleText("ЗАГР", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end loadBtn.DoClick = function() local curData = allPresets[cmdModel][tostring(i)] if not curData then return end for idx, val in pairs(curData.bgs) do cmdCurrentBodygroups[tonumber(idx) or idx] = val end if curData.skin then cmdCurrentSkin = curData.skin end surface.PlaySound("buttons/button15.wav") -- Обновляем правую панель чтобы отразить загруженный пресет RebuildSettings() end local saveBtn = vgui.Create("DButton", slotPanel) saveBtn:SetPos(48, btnY) saveBtn:SetSize(btnW, 25) saveBtn:SetText("") saveBtn.Paint = function(s, w, h) local col = s:IsHovered() and C_CMD_H or C_CMD draw.RoundedBox(4, 0, 0, w, h, col) draw.SimpleText("СОХР", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end saveBtn.DoClick = function() local finalName = nameEntry:GetText() if finalName == "" then finalName = defaultName end local safeBGs = {} for k, v in pairs(cmdCurrentBodygroups) do safeBGs[tostring(k)] = v end allPresets[cmdModel][tostring(i)] = { name = finalName, bgs = safeBGs, skin = cmdCurrentSkin } file.Write("wardrobe_presets.txt", util.TableToJSON(allPresets)) surface.PlaySound("buttons/button15.wav") end local delBtn = vgui.Create("DButton", slotPanel) delBtn:SetPos(87, btnY) delBtn:SetSize(btnW, 25) delBtn:SetText("") delBtn.Paint = function(s, w, h) if not allPresets[cmdModel][tostring(i)] then draw.RoundedBox(4, 0, 0, w, h, Color(30, 30, 30, 150)) draw.SimpleText("УДАЛ", "DermaDefault", w/2, h/2, Color(100, 100, 100), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) return end local col = s:IsHovered() and C_WARN_H or C_WARN draw.RoundedBox(4, 0, 0, w, h, col) draw.SimpleText("УДАЛ", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end delBtn.DoClick = function() if allPresets[cmdModel][tostring(i)] then allPresets[cmdModel][tostring(i)] = nil file.Write("wardrobe_presets.txt", util.TableToJSON(allPresets)) nameEntry:SetText(defaultName) surface.PlaySound("buttons/button15.wav") end end py = py + 95 end end -- === ЦЕНТРАЛЬНАЯ ПАНЕЛЬ: МОДЕЛЬ === local rightW = math.Clamp(fW * 0.38, 350, 550) local midW = fW - leftW - rightW - 80 local midX = 40 + leftW local rightX = midX + midW + 20 local modelPanel = vgui.Create("DModelPanel", frame) modelPanel:SetPos(midX, 60) modelPanel:SetSize(midW, fH - 140) modelPanel.bModelReady = false local oldCmdPaint = modelPanel.Paint modelPanel.Paint = function(s, w, h) if bgMat and not bgMat:IsError() then surface.SetDrawColor(255, 255, 255, 255) surface.SetMaterial(bgMat) surface.DrawTexturedRect(0, 0, w, h) end surface.SetDrawColor(C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) if not selectedMember then draw.SimpleText("Выберите бойца", "DermaLarge", w/2, h/2, Color(100, 100, 100), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) elseif s.bModelReady then oldCmdPaint(s, w, h) end end modelPanel.camAnims = { zoom = 60, rot = Angle(0, 180, 0), offset = Vector(0, 0, 0) } modelPanel.curZoom = 60 modelPanel.curRot = Angle(0, 180, 0) modelPanel.curOffset = Vector(0, 0, 0) modelPanel.LayoutEntity = function(s, ent) if not s.bAnimSet then local seq = ent:LookupSequence("idle_subtle") if seq <= 0 then seq = ent:LookupSequence("idle_all_01") end if seq > 0 then ent:SetSequence(seq) end s.bAnimSet = true end if s.bDragging then local x, y = gui.MousePos() local dx = x - s.lastX local dy = y - s.lastY if input.IsKeyDown(KEY_LSHIFT) or input.IsKeyDown(KEY_RSHIFT) then local right = s.curRot:Right() local up = s.curRot:Up() s.camAnims.offset = s.camAnims.offset - right * (dx * 0.15) + up * (dy * 0.15) elseif input.IsKeyDown(KEY_LALT) or input.IsKeyDown(KEY_RALT) then s.camAnims.rot.yaw = s.camAnims.rot.yaw - dx * 0.8 s.camAnims.rot.pitch = math.Clamp(s.camAnims.rot.pitch - dy * 0.8, -45, 45) end s.lastX, s.lastY = x, y end local speed = FrameTime() * 10 s.curZoom = Lerp(speed, s.curZoom, s.camAnims.zoom) s.curRot = LerpAngle(speed, s.curRot, s.camAnims.rot) s.curOffset = LerpVector(speed, s.curOffset, s.camAnims.offset) local targetPos = ent:GetPos() + Vector(0, 0, 40) + s.curOffset s:SetCamPos(targetPos - s.curRot:Forward() * s.curZoom) s:SetLookAng(s.curRot) ent:FrameAdvance((RealTime() - (s.lastTick or RealTime())) * 1) s.lastTick = RealTime() end modelPanel.OnMousePressed = function(s, code) if code == MOUSE_LEFT then local clickTime = SysTime() if s.lastClickTime and (clickTime - s.lastClickTime) < 0.3 then s.camAnims.zoom = 60 s.camAnims.rot = Angle(0, 180, 0) s.camAnims.offset = Vector(0, 0, 0) s.lastClickTime = 0 return end s.lastClickTime = clickTime s.bDragging = true s.lastX, s.lastY = gui.MousePos() end end modelPanel.OnMouseReleased = function(s, code) if code == MOUSE_LEFT then s.bDragging = false end end modelPanel.OnCursorExited = function(s) s.bDragging = false end modelPanel.OnMouseWheeled = function(s, delta) s.camAnims.zoom = math.Clamp(s.camAnims.zoom - delta * 15, 10, 120) end modelPanel.Think = function(s) local entity = s:GetEntity() if IsValid(entity) then for idx, value in pairs(cmdCurrentBodygroups) do if entity:GetBodygroup(idx) ~= value then entity:SetBodygroup(idx, value) end end if entity:GetSkin() ~= cmdCurrentSkin then entity:SetSkin(cmdCurrentSkin) end end end -- === КНОПКИ РАКУРСОВ === local viewButtons = vgui.Create("DPanel", frame) viewButtons:SetPos(midX, fH - 70) viewButtons:SetSize(midW, 50) viewButtons.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, C_BG_M) surface.SetDrawColor(C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) end local angles = {{"СПЕРЕДИ", "front"}, {"СБОКУ", "side"}, {"СЗАДИ", "back"}} for k, v in ipairs(angles) do local btn = vgui.Create("DButton", viewButtons) btn:SetText("") btn.Paint = function(s, w, h) local col = s:IsHovered() and C_CMD_H or C_CMD draw.RoundedBox(4, 0, 0, w, h, col) surface.SetDrawColor(C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) draw.SimpleText(v[1], "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end btn.DoClick = function() if v[2] == "front" then modelPanel.camAnims.rot = Angle(0, 180, 0) elseif v[2] == "side" then modelPanel.camAnims.rot = Angle(0, 90, 0) elseif v[2] == "back" then modelPanel.camAnims.rot = Angle(0, 0, 0) end modelPanel.camAnims.offset = Vector(0, 0, 0) end btn.PerformLayout = function(s, w, h) local bw = (viewButtons:GetWide() - 40) / 3 btn:SetSize(bw, 30) btn:SetPos(10 + (k - 1) * (bw + 10), 10) end end -- === ПРАВАЯ ПАНЕЛЬ: НАСТРОЙКИ БОДИГРУПП И СКИНОВ === local settingsPanel = vgui.Create("DPanel", frame) settingsPanel:SetPos(rightX, 60) settingsPanel:SetSize(rightW, fH - 80) settingsPanel.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, C_BG_M) surface.SetDrawColor(C_BDR) surface.DrawOutlinedRect(0, 0, w, h, 1) end local settingsScroll = vgui.Create("DScrollPanel", settingsPanel) settingsScroll:Dock(FILL) settingsScroll:DockMargin(10, 10, 10, 55) local sbar = settingsScroll:GetVBar() sbar:SetWide(4) sbar.Paint = function() end sbar.btnGrip.Paint = function(s, w, h) draw.RoundedBox(0, 0, 0, w, h, Color(100, 100, 100, 100)) end sbar.btnUp.Paint = function() end sbar.btnDown.Paint = function() end local baseCellSize = 108 RebuildSettings = function() settingsScroll:Clear() if not selectedMember then local lbl = vgui.Create("DLabel", settingsScroll) lbl:Dock(TOP) lbl:DockMargin(10, 20, 10, 0) lbl:SetTall(30) lbl:SetText("Выберите бойца из списка слева") lbl:SetFont("DermaDefaultBold") lbl:SetTextColor(Color(120, 120, 120)) return end local headerLabel = vgui.Create("DLabel", settingsScroll) headerLabel:Dock(TOP) headerLabel:DockMargin(22, 0, 30, 15) headerLabel:SetTall(20) headerLabel:SetText("ЭКИПИРОВКА: " .. string.upper(selectedMember.nick)) headerLabel:SetFont("DermaDefaultBold") headerLabel:SetTextColor(C_CMD_SEL) -- === БОДИГРУППЫ === if table.Count(cmdBodygroups) > 0 then local bgArray = {} for idx, data in pairs(cmdBodygroups) do table.insert(bgArray, data) end table.sort(bgArray, function(a, b) return a.index < b.index end) for _, data in ipairs(bgArray) do local idx = data.index local bgName = string.lower(data.name) local bgPanel = vgui.Create("DPanel", settingsScroll) bgPanel:Dock(TOP) bgPanel:DockMargin(10, 5, 30, 10) bgPanel.Expanded = true local panelExpandedHeight = 25 + baseCellSize bgPanel.CurHeight = panelExpandedHeight bgPanel:SetTall(panelExpandedHeight) bgPanel.Paint = function() end bgPanel.Think = function(s) local targetHeight = s.Expanded and panelExpandedHeight or 25 if s.CurHeight ~= targetHeight then s.CurHeight = Lerp(FrameTime() * 15, s.CurHeight, targetHeight) if math.abs(s.CurHeight - targetHeight) < 0.5 then s.CurHeight = targetHeight end s:SetTall(math.Round(s.CurHeight)) end end local headerBtn = vgui.Create("DButton", bgPanel) headerBtn:SetPos(22, 0) headerBtn:SetSize(336, 25) headerBtn:SetText("") headerBtn.Paint = function(s, w, h) local col = s:IsHovered() and Color(255, 255, 255) or Color(200, 200, 200) draw.SimpleText(string.upper(data.name), "DermaDefaultBold", 0, h/2, col, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) draw.SimpleText(bgPanel.Expanded and "▼" or "◀", "DermaDefault", w, h/2, col, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) end headerBtn.DoClick = function() bgPanel.Expanded = not bgPanel.Expanded; surface.PlaySound("UI/buttonclick.wav") end local carouselPanel = vgui.Create("DPanel", bgPanel) carouselPanel:SetPos(22, 25) carouselPanel:SetSize(336, baseCellSize) carouselPanel.Paint = function() end local startIdx = 0 for k2 = 0, data.count - 1 do if k2 == (cmdCurrentBodygroups[idx] or 0) then startIdx = k2; break end end carouselPanel.TargetScroll = startIdx carouselPanel.CurScroll = startIdx local leftArrow = vgui.Create("DButton", bgPanel) leftArrow:SetPos(0, 25); leftArrow:SetSize(16, baseCellSize); leftArrow:SetText("") leftArrow.Paint = function(s, w, h) local col = s:IsHovered() and C_ARMA_SLOT_HOVER or C_ARMA_SLOT_EMPTY draw.RoundedBox(0, 0, 0, w, h, col) draw.SimpleText("<", "DermaDefaultBold", w/2, h/2, s:IsHovered() and C_CMD_SEL or Color(150, 150, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) surface.SetDrawColor(Color(0, 0, 0, 120)); surface.DrawOutlinedRect(0, 0, w, h, 1) end local rightArrow = vgui.Create("DButton", bgPanel) rightArrow:SetPos(364, 25); rightArrow:SetSize(16, baseCellSize); rightArrow:SetText("") rightArrow.Paint = function(s, w, h) local col = s:IsHovered() and C_ARMA_SLOT_HOVER or C_ARMA_SLOT_EMPTY draw.RoundedBox(0, 0, 0, w, h, col) draw.SimpleText(">", "DermaDefaultBold", w/2, h/2, s:IsHovered() and C_CMD_SEL or Color(150, 150, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) surface.SetDrawColor(Color(0, 0, 0, 120)); surface.DrawOutlinedRect(0, 0, w, h, 1) end local maxScroll = math.max(0, data.count - 1) leftArrow.DoClick = function() carouselPanel.TargetScroll = math.max(0, carouselPanel.TargetScroll - 1); surface.PlaySound("UI/buttonclick.wav") end rightArrow.DoClick = function() carouselPanel.TargetScroll = math.min(maxScroll, carouselPanel.TargetScroll + 1); surface.PlaySound("UI/buttonclick.wav") end local cells = {} for i = 0, data.count - 1 do local cell = vgui.Create("DButton", carouselPanel) cell:SetText("") cell.DoClick = function() cmdCurrentBodygroups[idx] = i carouselPanel.TargetScroll = i surface.PlaySound("UI/buttonclick.wav") end cell.Paint = function(s, w, h) local isSelected = (cmdCurrentBodygroups[idx] or 0) == i local bgColor = C_ARMA_SLOT if isSelected then bgColor = C_CMD_SEL elseif s:IsHovered() then bgColor = C_ARMA_SLOT_HOVER end local alphaMult = math.Clamp(1 - (s.AbsOffset or 0) * 0.35, 0.2, 1) surface.SetDrawColor(bgColor.r, bgColor.g, bgColor.b, bgColor.a * alphaMult) surface.DrawRect(0, 0, w, h) local iconData = CUSTOM_ICONS_BG[bgName] local customIcon = iconData and (iconData[i] or iconData.default) if customIcon and not customIcon:IsError() then surface.SetDrawColor(255, 255, 255, 255 * alphaMult) surface.SetMaterial(customIcon) surface.DrawTexturedRect(4, 4, w - 8, h - 8) else local txtColor = isSelected and Color(40, 40, 40, 255 * alphaMult) or Color(150, 150, 150, 255 * alphaMult) draw.SimpleText(tostring(i), "DermaLarge", w/2, h/2, txtColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end surface.SetDrawColor(0, 0, 0, 120 * alphaMult) surface.DrawOutlinedRect(0, 0, w, h, 1) end table.insert(cells, cell) end carouselPanel.Think = function(s) s.CurScroll = Lerp(FrameTime() * 12, s.CurScroll, s.TargetScroll) for ci, cell in ipairs(cells) do local index = ci - 1 local offset = index - s.CurScroll local absOffset = math.abs(offset) cell.AbsOffset = absOffset local scale = math.Clamp(1 - absOffset * 0.25, 0.5, 1) local size = baseCellSize * scale local spacing = 65 local x = (s:GetWide() / 2) - (size / 2) + (offset * spacing) local y = (s:GetTall() / 2) - (size / 2) cell:SetSize(size, size) cell:SetPos(x, y) cell:SetZPos(100 - math.floor(absOffset * 10)) end end end end -- === СКИНЫ === if #cmdAvailableSkins > 1 then local skinPanel = vgui.Create("DPanel", settingsScroll) skinPanel:Dock(TOP) skinPanel:DockMargin(10, 5, 30, 10) skinPanel.Expanded = true local panelExpandedHeight = 25 + baseCellSize skinPanel.CurHeight = panelExpandedHeight skinPanel:SetTall(panelExpandedHeight) skinPanel.Paint = function() end skinPanel.Think = function(s) local targetHeight = s.Expanded and panelExpandedHeight or 25 if s.CurHeight ~= targetHeight then s.CurHeight = Lerp(FrameTime() * 15, s.CurHeight, targetHeight) if math.abs(s.CurHeight - targetHeight) < 0.5 then s.CurHeight = targetHeight end s:SetTall(math.Round(s.CurHeight)) end end local skinHeader = vgui.Create("DButton", skinPanel) skinHeader:SetPos(22, 0); skinHeader:SetSize(336, 25); skinHeader:SetText("") skinHeader.Paint = function(s, w, h) local col = s:IsHovered() and Color(255, 255, 255) or Color(200, 200, 200) draw.SimpleText("ВАРИАНТ КАМУФЛЯЖА", "DermaDefaultBold", 0, h/2, col, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) draw.SimpleText(skinPanel.Expanded and "▼" or "◀", "DermaDefault", w, h/2, col, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) end skinHeader.DoClick = function() skinPanel.Expanded = not skinPanel.Expanded; surface.PlaySound("UI/buttonclick.wav") end local skinCarousel = vgui.Create("DPanel", skinPanel) skinCarousel:SetPos(22, 25); skinCarousel:SetSize(336, baseCellSize) skinCarousel.Paint = function() end skinCarousel.TargetScroll = cmdCurrentSkin or 0 skinCarousel.CurScroll = skinCarousel.TargetScroll -- ИСПРАВЛЕНА ОПЕЧАТКА ТУТ: local skinLeft = vgui.Create("DButton", skinPanel) skinLeft:SetPos(0, 25); skinLeft:SetSize(16, baseCellSize); skinLeft:SetText("") skinLeft.Paint = function(s, w, h) local col = s:IsHovered() and C_ARMA_SLOT_HOVER or C_ARMA_SLOT_EMPTY draw.RoundedBox(0, 0, 0, w, h, col) draw.SimpleText("<", "DermaDefaultBold", w/2, h/2, s:IsHovered() and C_CMD_SEL or Color(150, 150, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) surface.SetDrawColor(Color(0, 0, 0, 120)); surface.DrawOutlinedRect(0, 0, w, h, 1) end local skinRight = vgui.Create("DButton", skinPanel) skinRight:SetPos(364, 25); skinRight:SetSize(16, baseCellSize); skinRight:SetText("") skinRight.Paint = function(s, w, h) local col = s:IsHovered() and C_ARMA_SLOT_HOVER or C_ARMA_SLOT_EMPTY draw.RoundedBox(0, 0, 0, w, h, col) draw.SimpleText(">", "DermaDefaultBold", w/2, h/2, s:IsHovered() and C_CMD_SEL or Color(150, 150, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) surface.SetDrawColor(Color(0, 0, 0, 120)); surface.DrawOutlinedRect(0, 0, w, h, 1) end local skinMaxScroll = math.max(0, #cmdAvailableSkins - 1) skinLeft.DoClick = function() skinCarousel.TargetScroll = math.max(0, skinCarousel.TargetScroll - 1); surface.PlaySound("UI/buttonclick.wav") end skinRight.DoClick = function() skinCarousel.TargetScroll = math.min(skinMaxScroll, skinCarousel.TargetScroll + 1); surface.PlaySound("UI/buttonclick.wav") end local skinCells = {} for i = 0, #cmdAvailableSkins - 1 do local cell = vgui.Create("DButton", skinCarousel) cell:SetText("") cell.DoClick = function() cmdCurrentSkin = i skinCarousel.TargetScroll = i surface.PlaySound("UI/buttonclick.wav") end cell.Paint = function(s, w, h) local isSelected = cmdCurrentSkin == i local bgColor = C_ARMA_SLOT if isSelected then bgColor = C_CMD_SEL elseif s:IsHovered() then bgColor = C_ARMA_SLOT_HOVER end local alphaMult = math.Clamp(1 - (s.AbsOffset or 0) * 0.35, 0.2, 1) surface.SetDrawColor(bgColor.r, bgColor.g, bgColor.b, bgColor.a * alphaMult) surface.DrawRect(0, 0, w, h) local txtColor = isSelected and Color(40, 40, 40, 255 * alphaMult) or Color(150, 150, 150, 255 * alphaMult) draw.SimpleText(tostring(i), "DermaLarge", w/2, h/2, txtColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) surface.SetDrawColor(0, 0, 0, 120 * alphaMult) surface.DrawOutlinedRect(0, 0, w, h, 1) end table.insert(skinCells, cell) end skinCarousel.Think = function(s) s.CurScroll = Lerp(FrameTime() * 12, s.CurScroll, s.TargetScroll) for ci, cell in ipairs(skinCells) do local index = ci - 1 local offset = index - s.CurScroll local absOffset = math.abs(offset) cell.AbsOffset = absOffset local scale = math.Clamp(1 - absOffset * 0.25, 0.5, 1) local size = baseCellSize * scale local spacing = 65 local x = (s:GetWide() / 2) - (size / 2) + (offset * spacing) local y = (s:GetTall() / 2) - (size / 2) cell:SetSize(size, size) cell:SetPos(x, y) cell:SetZPos(100 - math.floor(absOffset * 10)) end end end end -- === КНОПКИ ПРИМЕНИТЬ / СБРОС === local applyBtn = vgui.Create("DButton", settingsPanel) local wipeBtn = vgui.Create("DButton", settingsPanel) wipeBtn:SetSize(135, 35); wipeBtn:SetText("") wipeBtn.Paint = function(s, w, h) local col = s:IsHovered() and C_WARN_H or C_WARN draw.RoundedBox(8, 0, 0, w, h, C_BDR) draw.RoundedBox(8, 2, 2, w - 4, h - 4, col) draw.SimpleText("СБРОСИТЬ", "DermaDefaultBold", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end wipeBtn.DoClick = function() if not selectedMember then return end net.Start("SandboxWardrobeCommanderApply") net.WriteUInt(selectedMember.entIndex, 16) net.WriteBool(true) net.SendToServer() surface.PlaySound("UI/buttonclick.wav") end applyBtn:SetSize(135, 35); applyBtn:SetText("") applyBtn.Paint = function(s, w, h) local col = s:IsHovered() and C_CMD_H or C_CMD draw.RoundedBox(8, 0, 0, w, h, C_BDR) draw.RoundedBox(8, 2, 2, w - 4, h - 4, col) draw.RoundedBoxEx(8, 2, 2, w - 4, h/2 - 2, C_GRAD, true, true, false, false) draw.SimpleText("ПРИМЕНИТЬ", "DermaDefaultBold", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end applyBtn.DoClick = function() if not selectedMember then return end net.Start("SandboxWardrobeCommanderApply") net.WriteUInt(selectedMember.entIndex, 16) net.WriteBool(false) net.WriteTable(cmdCurrentBodygroups) net.WriteUInt(cmdCurrentSkin, 8) net.SendToServer() surface.PlaySound("UI/buttonclick.wav") end settingsPanel.PerformLayout = function(s, w, h) applyBtn:SetPos(w - 135 - 10, h - 35 - 10) wipeBtn:SetPos(w - 135 * 2 - 20, h - 35 - 10) end -- Инициальный rebuild RebuildSettings() RebuildCommanderPresets() -- === Выбор бойца === memberList.OnRowSelected = function(lst, index, pnl) selectedMember = pnl.memberData if not selectedMember then return end cmdModel = selectedMember.model cmdCurrentBodygroups = {} cmdCurrentSkin = 0 cmdBodygroups = {} cmdAvailableSkins = {} -- Обновляем модель modelPanel:SetModel(cmdModel) modelPanel.bAnimSet = false modelPanel.bModelReady = false modelPanel.camAnims.zoom = 60 modelPanel.camAnims.rot = Angle(0, 180, 0) modelPanel.camAnims.offset = Vector(0, 0, 0) -- Задержка нужна чтобы DModelPanel успел обновить Entity после SetModel timer.Simple(0.1, function() if not IsValid(modelPanel) then return end modelPanel.bModelReady = true local ent = modelPanel:GetEntity() if IsValid(ent) then for i = 0, ent:GetNumBodyGroups() - 1 do local name = ent:GetBodygroupName(i) local count = ent:GetBodygroupCount(i) if count > 1 then cmdBodygroups[i] = { name = name, count = count, index = i } cmdCurrentBodygroups[i] = 0 end end local skinCount = ent:SkinCount() or 0 for i = 0, skinCount - 1 do table.insert(cmdAvailableSkins, i) end end -- Пробуем получить текущие бодигруппы от серверного игрока local target = Entity(selectedMember.entIndex) if IsValid(target) then for idx in pairs(cmdBodygroups) do cmdCurrentBodygroups[idx] = target:GetBodygroup(idx) end cmdCurrentSkin = target:GetSkin() end RebuildSettings() RebuildCommanderPresets() end) end end)