local PLUGIN = PLUGIN -- Материалы PLUGIN.backgroundMaterial = Material("materials/ft_ui/military/vnu/charcreate/bg.png") PLUGIN.logoMaterial = Material("materials/ft_ui/military/vnu/charcreate/logo.png") if CLIENT then if not ix.currency._originalGet then ix.currency._originalGet = ix.currency.Get end function ix.currency.Get(amount, noFormat) amount = tonumber(amount) or 0 local formatted = tostring(math.floor(amount)):reverse():gsub("(%d%d%d)", "%1 "):reverse():gsub("^ ", "") local client = LocalPlayer() if not IsValid(client) then return formatted end local char = client:GetCharacter() if not char then return formatted end local faction = char:GetFaction() if faction == FACTION_UKRAINE then return formatted .. "$" end if faction == FACTION_RUSSIAN then return formatted .. "₽" end return formatted end end -- Масштабирование local function GetScale() local scale = ScrH() / 1080 return math.Clamp(scale, 0.5, 2.0) end local function ScaleSize(val) return math.max(1, math.Round(val * GetScale())) end local function ScalePos(val) return math.Round(val * GetScale()) end local function CreateF4MenuFonts() surface.CreateFont("F4Menu_Title", { font = "exo2", size = math.max(ScaleSize(25), 14), weight = 400 }) surface.CreateFont("F4Menu_Username", { font = "exo2", size = math.max(ScaleSize(18), 12), weight = 600 }) surface.CreateFont("F4Menu_Balance", { font = "exo2", size = math.max(ScaleSize(18), 12), weight = 600 }) surface.CreateFont("F4Menu_Category", { font = "exo2", size = math.max(ScaleSize(20), 13), weight = 500 }) surface.CreateFont("F4Menu_Item", { font = "exo2", size = math.max(ScaleSize(16), 11), weight = 400 }) surface.CreateFont("F4Menu_InfoHeader", { font = "exo2", size = math.max(ScaleSize(28), 16), weight = 700 }) surface.CreateFont("F4Menu_InfoValue", { font = "exo2", size = math.max(ScaleSize(22), 14), weight = 500 }) surface.CreateFont("F4Menu_InfoSmall", { font = "exo2", size = math.max(ScaleSize(15), 10), weight = 400 }) surface.CreateFont("F4Menu_RulesTitle", { font = "exo2", size = math.max(ScaleSize(22), 13), weight = 400 }) surface.CreateFont("F4Menu_RulesText", { font = "exo2", size = math.max(ScaleSize(20), 12), weight = 400 }) surface.CreateFont("F4Menu_DonateHero", { font = "exo2", size = math.max(ScaleSize(30), 18), weight = 700 }) surface.CreateFont("F4Menu_DonateSub", { font = "exo2", size = math.max(ScaleSize(18), 12), weight = 400 }) surface.CreateFont("F4Menu_DonatePrice", { font = "exo2", size = math.max(ScaleSize(28), 16), weight = 700 }) surface.CreateFont("F4Menu_DonateBalance", { font = "exo2", size = math.max(ScaleSize(26), 15), weight = 600 }) surface.CreateFont("F4Menu_DonateTag", { font = "exo2", size = math.max(ScaleSize(16), 10), weight = 600 }) surface.CreateFont("F4Menu_DonateTitle", { font = "exo2", size = math.max(ScaleSize(25), 14), weight = 500 }) surface.CreateFont("F4Menu_DonateBold", { font = "exo2", size = math.max(ScaleSize(25), 14), weight = 700 }) surface.CreateFont("F4Menu_ItemSmall", { font = "exo2", size = math.max(ScaleSize(19), 11), weight = 300 }) end CreateF4MenuFonts() hook.Add("OnScreenSizeChanged", "F4Menu_UpdateFonts", CreateF4MenuFonts) PLUGIN.infoData = PLUGIN.infoData or {} PLUGIN.infoRequestAt = 0 local function FormatNumber(value) local number = tonumber(value) if not number then return "—" end local negative = number < 0 number = math.floor(math.abs(number)) local text = tostring(number) while true do local formatted, k = string.gsub(text, "^(%d+)(%d%d%d)", "%1 %2") text = formatted if k == 0 then break end end if negative then text = "-" .. text end return text end local function FormatList(list, limit) if not istable(list) or #list == 0 then return "—" end limit = limit or 4 local buffer = {} for i = 1, math.min(#list, limit) do buffer[#buffer + 1] = tostring(list[i]) end if #list > limit then buffer[#buffer + 1] = string.format("…%d", #list - limit) end return table.concat(buffer, ", ") end local DONATE_NOTICE_COLOR = Color(233, 184, 73) local function FormatDonatePrice(value, currency) if not value then return currency or "—" end return string.format("%s %s", FormatNumber(value), currency or "₽") end local function NotifyDonate(message) local ply = LocalPlayer() if IsValid(ply) and ply.Notify then ply:Notify(message) else chat.AddText(DONATE_NOTICE_COLOR, "[Донат] ", color_white, message) end end function PLUGIN:GetDonateBalance() local client = LocalPlayer() if not IsValid(client) then return 0 end local balance = 0 if isfunction(client.IGSFunds) then local ok, value = pcall(client.IGSFunds, client) if ok and isnumber(value) then balance = value end elseif isfunction(client.GetIGSFunds) then local ok, value = pcall(client.GetIGSFunds, client) if ok and isnumber(value) then balance = value end elseif client.GetNetVar then balance = client:GetNetVar("igsFunds", 0) or 0 end balance = math.floor(tonumber(balance) or 0) if balance < 0 then balance = 0 end return balance end function PLUGIN:SendDonatePurchase(entry, price, mode) if not entry then return false end if entry.externalOnly and entry.url and gui and gui.OpenURL then gui.OpenURL(entry.url) return true end if entry.netString then net.Start(entry.netString) net.WriteString(entry.id or "") net.WriteUInt(price or 0, 32) net.WriteString(mode or "month") net.SendToServer() return true end if entry.command and IsValid(LocalPlayer()) then LocalPlayer():ConCommand(entry.command) return true end if not entry.id or entry.id == "" then return false end net.Start("ix.F4_DonatePurchase") net.WriteString(entry.id) net.WriteUInt(price or entry.price or 0, 32) net.WriteString(mode or "month") net.SendToServer() return true end function PLUGIN:ConfirmDonatePurchase(entry, selectedPrice, selectedMode) if not entry then return end local price = selectedPrice or entry.priceMonth or entry.price or 0 local mode = selectedMode or "month" local priceText = FormatDonatePrice(price, entry.currency or "RUB") local modeText = "" if entry.priceMonth or entry.priceForever then modeText = mode == "forever" and " (навсегда)" or " (на месяц)" end Derma_Query( string.format("Приобрести «%s»%s за %s?", entry.title or "позицию", modeText, priceText or "—"), "Подтверждение покупки", "Купить", function() if not self:SendDonatePurchase(entry, price, mode) then NotifyDonate("Серверная обработка покупки недоступна. Используйте сайт проекта.") end end, "Отмена" ) end local function ResolveColor(data, alpha) if istable(data) then local r = data.r or data[1] or 56 local g = data.g or data[2] or 84 local b = data.b or data[3] or 45 return Color(r, g, b, alpha or 255) end return Color(56, 84, 45, alpha or 255) end local function ShortModel(path) if not isstring(path) or path == "" then return "—" end local parts = string.Split(path, "/") return parts[#parts] or path end function PLUGIN:RequestInfoData(force) local now = CurTime() if not force and self.infoRequestAt and (now - self.infoRequestAt) < 3 then return end self.infoRequestAt = now net.Start("ix.F4_RequestInfo") net.SendToServer() end net.Receive("ix.F4_SendInfo", function() local plugin = ix.plugin.Get("f4menu") if not plugin then return end plugin.infoData = net.ReadTable() or {} plugin.infoData.receivedAt = CurTime() if plugin.activeTab == "Информация" and IsValid(plugin.contentPanel) then plugin:CreateInfoTab(true) end end) net.Receive("ix.F4_DonatePurchaseResult", function() local plugin = ix.plugin.Get("f4menu") if not plugin then return end local success = net.ReadBool() local message = net.ReadString() local productID = net.ReadString() if message and message ~= "" then NotifyDonate(message) end -- Принудительно обновляем баланс после покупки timer.Simple(0.5, function() if success and plugin.activeTab == "Донат" and IsValid(plugin.contentPanel) then plugin:CreateDonateTab() end end) end) -- Переменная для задержки PLUGIN.lastF4Press = 0 PLUGIN.f4Cooldown = 1 function PLUGIN:CreateF4Menu() if IsValid(self.f4Menu) then self.f4Menu:Remove() end local scrW, scrH = ScrW(), ScrH() CreateF4MenuFonts() self.f4Menu = vgui.Create("DFrame") self.f4Menu:SetSize(scrW, scrH) self.f4Menu:SetPos(0, 0) self.f4Menu:SetTitle("") self.f4Menu:SetDraggable(false) self.f4Menu:ShowCloseButton(false) self.f4Menu:MakePopup() -- Закрытие меню по Esc self.f4Menu.OnKeyCodePressed = function(s, key) if key == KEY_ESCAPE then s:Close() gui.HideGameUI() return true end end -- Отключаем стандартное поведение фрейма self.f4Menu:SetPaintBackgroundEnabled(false) self.f4Menu:SetBackgroundBlur(false) -- Основной фон self.f4Menu.Paint = function(s, w, h) -- Фоновое изображение if self.backgroundMaterial then surface.SetDrawColor(color_white) surface.SetMaterial(self.backgroundMaterial) surface.DrawTexturedRect(0, 0, w, h) end -- Темный оверлей с размытием surface.SetDrawColor(Color(1, 48, 21, 140)) surface.DrawRect(0, 74, w, h - 74) surface.SetDrawColor(Color(10, 10, 10, 140)) surface.DrawRect(0, 74, w, h - 74) -- Верхняя панель surface.SetDrawColor(Color(13, 13, 13)) surface.DrawRect(0, 0, w, 74) -- Декоративные линии на верхней панели local linePositions = { {555, 7}, {718, 7}, {852.5, 7}, {950, 7}, {1049, 7}, {1184, 7}, {1317, 7} } surface.SetDrawColor(Color(21, 21, 21)) for _, pos in ipairs(linePositions) do surface.DrawRect(pos[1], pos[2], 3, 52) end end -- Логотип local logo = vgui.Create("DImage", self.f4Menu) logo:SetSize(ScaleSize(124), ScaleSize(44)) logo:SetPos(ScalePos(37), ScalePos(15)) if self.logoMaterial then logo:SetMaterial(self.logoMaterial) end -- Аватар пользователя local avatar = vgui.Create("AvatarImage", self.f4Menu) avatar:SetSize(ScaleSize(50), ScaleSize(50)) avatar:SetPos(ScalePos(220), ScalePos(14)) avatar:SetPlayer(LocalPlayer(), 64) -- Имя пользователя local username = vgui.Create("DLabel", self.f4Menu) username:SetPos(ScalePos(280), ScalePos(17)) username:SetSize(ScaleSize(200), ScaleSize(22)) username:SetFont("F4Menu_Username") username:SetTextColor(Color(56, 84, 45)) local character = LocalPlayer():GetCharacter() if character then username:SetText(character:GetName()) else username:SetText("Игрок") end -- Баланс local balance = vgui.Create("DLabel", self.f4Menu) balance:SetPos(ScalePos(280), ScalePos(39)) balance:SetSize(ScaleSize(200), ScaleSize(22)) balance:SetFont("F4Menu_Balance") balance:SetTextColor(color_white) if character then local money = character:GetMoney() balance:SetText("Баланс: " .. ix.currency.Get(money)) else balance:SetText("Баланс: 0") end -- Меню навигации local menuItems = { {"Информация", ScalePos(580), ScaleSize(145), ScaleSize(52), ScalePos(576)}, {"Правила", ScalePos(727.5), ScaleSize(120), ScaleSize(52), ScalePos(736)}, {"Донат", ScalePos(842), ScaleSize(80), ScaleSize(52), ScalePos(870)}, {"Отряды", ScalePos(942), ScaleSize(90), ScaleSize(52), ScalePos(965)}, {"Настройки", ScalePos(1060), ScaleSize(120), ScaleSize(52), ScalePos(1068)}, {"Спасибо", ScalePos(1192), ScaleSize(120), ScaleSize(52), ScalePos(1201)} } self.activeTab = self.activeTab or "Информация" -- Сначала создаем подсветки для активной вкладки for _, item in ipairs(menuItems) do if self.activeTab == item[1] then local highlight = vgui.Create("DPanel", self.f4Menu) highlight:SetSize(item[3], item[4]) highlight:SetPos(item[5] - ScalePos(9), ScalePos(9)) highlight.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, Color(1, 67, 29)) draw.RoundedBox(8, 0+1, 0+1, w-2, h-2, Color(21, 21, 21)) end break end end -- Создаем кастомные кнопки вкладок for _, item in ipairs(menuItems) do local menuBtn = vgui.Create("DButton", self.f4Menu) menuBtn:SetPos(item[2], ScalePos(19)) menuBtn:SetSize(ScaleSize(120), ScaleSize(30)) menuBtn:SetText("") menuBtn:SetPaintBackground(false) menuBtn:NoClipping(true) menuBtn.Paint = function(s, w, h) --if self.activeTab == item[1] then -- surface.SetDrawColor(Color(21, 21, 21)) -- surface.DrawRect(0, 0, w, h) -- surface.SetDrawColor(Color(1, 67, 29)) -- surface.DrawOutlinedRect(0, 0, w, h, 1) --end draw.SimpleText(item[1], "F4Menu_Title", w/2, h/2, self.activeTab == item[1] and color_white or Color(194, 194, 194), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end menuBtn.DoClick = function(s) s:MouseCapture(false) if item[1] == "Донат" then if IGS and IGS.UI then IGS.UI() else RunConsoleCommand("igs") end return end self.activeTab = item[1] self:SwitchTab(item[1]) if IsValid(self.f4Menu) then self:CreateF4Menu() end end menuBtn.OnMousePressed = function(s, code) if code == MOUSE_LEFT then s:DoClick() return true end end menuBtn.OnCursorEntered = function(s) s:SetCursor("hand") end menuBtn.OnCursorExited = function(s) s:SetCursor("arrow") end end -- Социальные иконки local socialIcons = { {ScalePos(1700), "discord", "Discord"}, {ScalePos(1750), "steam", "Steam"}, {ScalePos(1800), "tiktok", "TikTok"}, {ScalePos(1850), "telegram", "Telegram"} } -- Загружаем материалы иконок local iconMaterials = {} for key, path in pairs(PLUGIN.socialIcons or {}) do iconMaterials[key] = Material(path, "smooth") end for _, icon in ipairs(socialIcons) do local socialBtn = vgui.Create("DButton", self.f4Menu) socialBtn:SetSize(ScaleSize(35), ScaleSize(35)) socialBtn:SetPos(icon[1], ScalePos(18)) socialBtn:SetText("") socialBtn:SetTooltip("Открыть " .. icon[3]) local iconMat = iconMaterials[icon[2]] socialBtn.Paint = function(s, w, h) -- Иконка if iconMat and not iconMat:IsError() then surface.SetDrawColor(Color(255, 255, 255)) surface.SetMaterial(iconMat) surface.DrawTexturedRect(0, 0, w, h) else -- Запасной вариант если материал не найден surface.SetDrawColor(Color(194, 194, 194)) surface.DrawRect(w/4, h/4, w/2, h/2) end -- Подсветка при наведении if s:IsHovered() then surface.SetDrawColor(Color(255, 255, 255, 30)) surface.DrawRect(0, 0, w, h) end end socialBtn.DoClick = function(s) s:MouseCapture(false) local link = PLUGIN.socialLinks[icon[2]] if link then gui.OpenURL(link) chat.AddText(Color(0, 255, 0), "Открыта ссылка: " .. icon[3]) else chat.AddText(Color(255, 100, 100), "Ссылка не настроена для: " .. icon[3]) end end socialBtn.OnMousePressed = function(s, code) if code == MOUSE_LEFT then s:DoClick() return true end end end -- Основная контентная область self.contentPanel = vgui.Create("DPanel", self.f4Menu) self.contentPanel:SetSize(ScaleSize(1500), ScaleSize(750)) self.contentPanel:SetPos(ScalePos(210), ScalePos(165)) self.contentPanel.Paint = function(s, w, h) draw.RoundedBox(15, 0, 0, w, h, Color(13, 13, 13, 218)) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) end -- Инициализируем активную вкладку self:SwitchTab(self.activeTab) -- Обработка клавиши F4 для закрытия self.f4Menu.OnKeyCodePressed = function(panel, key) if key == KEY_F4 then local currentTime = CurTime() if currentTime - self.lastF4Press >= self.f4Cooldown then self.lastF4Press = currentTime panel:Remove() end return true end end -- Блокируем все другие клавиши self.f4Menu.OnKeyCode = function(panel, key) return true end -- Блокируем клики по фону self.f4Menu.OnMousePressed = function(panel, code) return true end -- Блокируем колесо мыши self.f4Menu.OnMouseWheeled = function(panel, delta) return true end end function PLUGIN:SwitchTab(tabName) if not IsValid(self.contentPanel) then return end -- Очищаем предыдущий контент self.contentPanel:Clear() -- Контент в зависимости от вкладки if tabName == "Информация" then self:CreateInfoTab() elseif tabName == "Правила" then self:CreateRulesTab() elseif tabName == "Донат" then self:CreateDonateTab() elseif tabName == "Отряды" then self:CreateSquadsTab() elseif tabName == "Настройки" then self:CreateSettingsTab() elseif tabName == "Спасибо" then self:CreateThanksTab() end end function PLUGIN:OpenChangeNameDialog(character) if not character then return end local frame = vgui.Create("DFrame") frame:SetSize(ScaleSize(500), ScaleSize(200)) frame:Center() frame:SetTitle("") frame:SetDraggable(true) frame:ShowCloseButton(false) frame:MakePopup() frame.Paint = function(s, w, h) draw.RoundedBox(18, 0, 0, w, h, Color(13, 13, 13, 240)) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 2) draw.SimpleText("ИЗМЕНИТЬ ИМЯ", "F4Menu_Category", w/2, 20, color_white, TEXT_ALIGN_CENTER) end local textEntry = frame:Add("DTextEntry") textEntry:SetPos(ScalePos(30), ScalePos(70)) textEntry:SetSize(ScaleSize(440), ScaleSize(40)) textEntry:SetFont("F4Menu_Item") textEntry:SetTextColor(color_white) textEntry:SetCursorColor(color_white) textEntry:SetText(character:GetName()) textEntry:SetPlaceholderText("Введите новое имя...") textEntry.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, Color(18, 18, 18)) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) s:DrawTextEntryText(color_white, color_white, color_white) end local confirmBtn = frame:Add("DButton") confirmBtn:SetPos(ScalePos(30), ScalePos(130)) confirmBtn:SetSize(ScaleSize(200), ScaleSize(40)) confirmBtn:SetText("") confirmBtn.DoClick = function() local newName = textEntry:GetValue() if newName and newName ~= "" and newName ~= character:GetName() then net.Start("ixChangeName") net.WriteString(newName) net.SendToServer() frame:Close() end end confirmBtn.Paint = function(s, w, h) local col = s:IsHovered() and Color(56, 102, 35) or Color(27, 94, 32) draw.RoundedBox(8, 0, 0, w, h, col) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) draw.SimpleText("ПОДТВЕРДИТЬ", "F4Menu_Category", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end local cancelBtn = frame:Add("DButton") cancelBtn:SetPos(ScalePos(270), ScalePos(130)) cancelBtn:SetSize(ScaleSize(200), ScaleSize(40)) cancelBtn:SetText("") cancelBtn.DoClick = function() frame:Close() end cancelBtn.Paint = function(s, w, h) local col = s:IsHovered() and Color(50, 18, 18) or Color(35, 13, 13) draw.RoundedBox(8, 0, 0, w, h, col) surface.SetDrawColor(Color(67, 1, 1)) surface.DrawOutlinedRect(0, 0, w, h, 1) draw.SimpleText("ОТМЕНА", "F4Menu_Category", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end end function PLUGIN:CreateInfoTab(skipRequest) if not IsValid(self.contentPanel) then return end self.contentPanel:Clear() local character = LocalPlayer():GetCharacter() if not character then local warn = vgui.Create("DLabel", self.contentPanel) warn:SetPos(20, 20) warn:SetSize(self.contentPanel:GetWide() - 40, 40) warn:SetFont("F4Menu_Category") warn:SetText("Нет активного персонажа") warn:SetTextColor(color_white) warn:SetContentAlignment(5) return end -- Запрос данных с сервера if not skipRequest then local needData = true if self.infoData.character and self.infoData.receivedAt then needData = (CurTime() - self.infoData.receivedAt) > 15 end if needData then self:RequestInfoData() end end local data = self.infoData or {} local charData = data.character or {} local factionData = data.faction or {} local factionTable = ix.faction.Get(character:GetFaction()) -- Получаем данные local charName = character:GetName() local money = charData.money or character:GetMoney() local moneyText = ix.currency.Get(money) local factionName = (factionData.name and factionData.name ~= "") and factionData.name or (factionTable and L(factionTable.name) or "Неизвестно") local rankName = (charData.rank and charData.rank.name) or (LocalPlayer().GetRankName and LocalPlayer():GetRankName()) or "—" if rankName == false then rankName = "—" end local unitData = charData.subdivision or {} local specData = charData.spec or {} local unitName = unitData.name or (LocalPlayer().GetPodrName and LocalPlayer():GetPodrName()) or "—" local specName = specData.name or (LocalPlayer().GetSpecName and LocalPlayer():GetSpecName()) or "—" if unitName == false then unitName = "—" end if specName == false then specName = "—" end local techPoints = factionData.techPoints or 0 local supplyPoints = factionData.supplyPoints or 0 -- Левая панель с моделью local modelPanel = vgui.Create("DPanel", self.contentPanel) modelPanel:SetPos(ScalePos(20), ScalePos(20)) modelPanel:SetSize(ScaleSize(400), ScaleSize(710)) modelPanel.Paint = function(s, w, h) draw.RoundedBox(15, 0, 0, w, h, Color(13, 13, 13, 218)) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) end -- DModelPanel local modelView = vgui.Create("DModelPanel", modelPanel) modelView:SetPos(ScalePos(10), ScalePos(10)) modelView:SetSize(ScaleSize(380), ScaleSize(480)) modelView:SetModel(LocalPlayer():GetModel()) modelView:SetFOV(45) local eyepos = modelView.Entity:GetBonePosition(modelView.Entity:LookupBone("ValveBiped.Bip01_Head1")) if eyepos then eyepos:Add(Vector(0, 0, 2)) else eyepos = modelView.Entity:GetPos() eyepos:Add(Vector(0, 0, 64)) end modelView:SetLookAt(eyepos) modelView:SetCamPos(eyepos + Vector(50, 0, 0)) modelView.LayoutEntity = function(self, ent) if self.bAnimated then self:RunAnimation() end end -- Имя персонажа под моделью local nameLabel = vgui.Create("DLabel", modelPanel) nameLabel:SetPos(0, ScalePos(500)) nameLabel:SetSize(ScaleSize(400), ScaleSize(40)) nameLabel:SetFont("F4Menu_InfoHeader") nameLabel:SetText(charName) nameLabel:SetTextColor(color_white) nameLabel:SetContentAlignment(5) -- Фракция под именем local factionLabel = vgui.Create("DLabel", modelPanel) factionLabel:SetPos(0, ScalePos(545)) factionLabel:SetSize(ScaleSize(400), ScaleSize(30)) factionLabel:SetFont("F4Menu_Category") factionLabel:SetText(factionName) factionLabel:SetTextColor(Color(1, 67, 29)) factionLabel:SetContentAlignment(5) -- Баланс внизу local balanceLabel = vgui.Create("DLabel", modelPanel) balanceLabel:SetPos(0, ScalePos(590)) balanceLabel:SetSize(ScaleSize(400), ScaleSize(30)) balanceLabel:SetFont("F4Menu_RulesTitle") balanceLabel:SetText("Баланс: " .. moneyText) balanceLabel:SetTextColor(Color(165, 214, 167)) balanceLabel:SetContentAlignment(5) -- Кнопка изменения имени local changeNameBtn = vgui.Create("DButton", modelPanel) changeNameBtn:SetPos(ScalePos(20), ScalePos(640)) changeNameBtn:SetSize(ScaleSize(360), ScaleSize(40)) changeNameBtn:SetText("") changeNameBtn.DoClick = function() self:OpenChangeNameDialog(character) end changeNameBtn.Paint = function(s, w, h) local col = s:IsHovered() and Color(56, 102, 35) or Color(27, 94, 32) draw.RoundedBox(8, 0, 0, w, h, col) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) draw.SimpleText("ИЗМЕНИТЬ ИМЯ", "F4Menu_Category", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end -- Правая панель с информацией local infoPanel = vgui.Create("DPanel", self.contentPanel) infoPanel:SetPos(ScalePos(440), ScalePos(20)) infoPanel:SetSize(ScaleSize(1040), ScaleSize(710)) infoPanel.Paint = function(s, w, h) draw.RoundedBox(15, 0, 0, w, h, Color(13, 13, 13, 218)) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) end -- Заголовок local titleLabel = vgui.Create("DLabel", infoPanel) titleLabel:SetPos(ScalePos(25), ScalePos(20)) titleLabel:SetFont("F4Menu_DonateHero") titleLabel:SetText("ИНФОРМАЦИЯ О ПЕРСОНАЖЕ") titleLabel:SetTextColor(color_white) titleLabel:SizeToContents() -- Создаем строки информации local infoRows = { {label = "Звание:", value = rankName}, {label = "Подразделение:", value = unitName}, {label = "Специализация:", value = specName}, {label = "Очки техники фракции:", value = FormatNumber(techPoints)}, {label = "Очки снабжения фракции:", value = FormatNumber(supplyPoints)}, } local startY = ScalePos(100) local rowHeight = ScaleSize(70) for i, row in ipairs(infoRows) do local yPos = startY + (i - 1) * rowHeight -- Панель строки local rowPanel = vgui.Create("DPanel", infoPanel) rowPanel:SetPos(ScalePos(25), yPos) rowPanel:SetSize(ScaleSize(990), ScaleSize(60)) rowPanel.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, Color(21, 21, 21, 230)) surface.SetDrawColor(Color(1, 67, 29, 100)) surface.DrawOutlinedRect(0, 0, w, h, 1) -- Лейбл draw.SimpleText(row.label, "F4Menu_Category", 20, h/2, Color(165, 214, 167), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) -- Значение draw.SimpleText(row.value, "F4Menu_InfoValue", w - 20, h/2, color_white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) end end end function PLUGIN:CreateDonateTab() if not IsValid(self.contentPanel) then return end self.contentPanel:Clear() local primaryGreen = Color(38, 166, 91) local darkGreen = Color(16, 82, 46) local cardBg = Color(20, 20, 22) local cardHover = Color(25, 25, 28) local accentOrange = Color(255, 149, 0) local catalog = self:GetDonateCatalog() if not self.activeDonateCategory or not self:GetDonateCategory(self.activeDonateCategory) then self.activeDonateCategory = catalog[1] and catalog[1].id or nil end local BuildProducts -- Hero section with balance local heroPanel = self.contentPanel:Add("DPanel") heroPanel:SetPos(ScalePos(12), ScalePos(11)) heroPanel:SetSize(ScaleSize(1475), ScaleSize(110)) heroPanel.Paint = function(s, w, h) -- Gradient background surface.SetDrawColor(15, 15, 17, 250) surface.DrawRect(0, 0, w, h) draw.RoundedBox(8, 0, 0, w, h, Color(15, 15, 17, 0)) -- Top accent line surface.SetDrawColor(primaryGreen) surface.DrawRect(0, 0, w, 3) -- Subtle border surface.SetDrawColor(30, 30, 35) surface.DrawOutlinedRect(0, 0, w, h, 1) end -- Balance display local balanceLabel = vgui.Create("DLabel", heroPanel) balanceLabel:SetPos(ScalePos(25), ScalePos(18)) balanceLabel:SetFont("F4Menu_DonateTitle") balanceLabel:SetText("ВАШ БАЛАНС РУБ") balanceLabel:SetTextColor(Color(150, 150, 150)) balanceLabel:SizeToContents() local balanceAmount = vgui.Create("DLabel", heroPanel) balanceAmount:SetPos(ScalePos(25), ScalePos(43)) balanceAmount:SetFont("F4Menu_DonateHero") balanceAmount:SetTextColor(primaryGreen) local function UpdateBalance() local balance = self:GetDonateBalance() balanceAmount:SetText(string.Comma(balance) .. " РУБ.") balanceAmount:SizeToContents() end UpdateBalance() -- Auto-refresh balance every 5 seconds local balanceTimer = "F4Menu_BalanceRefresh_" .. CurTime() timer.Create(balanceTimer, 5, 0, function() if IsValid(balanceAmount) and IsValid(self.f4Menu) and self.f4Menu:IsVisible() then UpdateBalance() else timer.Remove(balanceTimer) end end) -- Topup button local topupBtn = vgui.Create("DButton", heroPanel) topupBtn:SetPos(ScalePos(25), ScalePos(73)) topupBtn:SetSize(ScaleSize(220), ScaleSize(28)) topupBtn:SetText("") topupBtn:SetCursor("hand") topupBtn.Paint = function(s, w, h) local hovered = s:IsHovered() local bg = hovered and Color(48, 186, 111) or primaryGreen draw.RoundedBox(6, 0, 0, w, h, bg) draw.SimpleText("ПОПОЛНИТЬ", "F4Menu_Category", w / 2, h / 2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end topupBtn.DoClick = function() surface.PlaySound("ui/buttonclickrelease.wav") local steamID = LocalPlayer():SteamID() local url = "https://gm-donate.net/donate/6282?steamid=" .. steamID gui.OpenURL(url) end -- Promo button local promoBtn = vgui.Create("DButton", heroPanel) promoBtn:SetPos(ScalePos(260), ScalePos(73)) promoBtn:SetSize(ScaleSize(180), ScaleSize(28)) promoBtn:SetText("") promoBtn:SetCursor("hand") promoBtn.Paint = function(s, w, h) local hovered = s:IsHovered() local alpha = hovered and 255 or 200 draw.RoundedBox(6, 0, 0, w, h, Color(30, 30, 35, alpha)) surface.SetDrawColor(60, 60, 70) surface.DrawOutlinedRect(0, 0, w, h, 1) draw.SimpleText("ПРОМОКОД", "F4Menu_Category", w / 2, h / 2, Color(200, 200, 200), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end promoBtn.DoClick = function() surface.PlaySound("ui/buttonclickrelease.wav") -- Открываем окно ввода промокода local promoFrame = vgui.Create("DFrame") promoFrame:SetSize(ScaleSize(500), ScaleSize(200)) promoFrame:Center() promoFrame:SetTitle("") promoFrame:SetDraggable(false) promoFrame:ShowCloseButton(false) promoFrame:MakePopup() promoFrame.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, Color(25, 25, 28)) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawRect(0, 0, w, 3) draw.SimpleText("АКТИВАЦИЯ ПРОМОКОДА", "F4Menu_Category", w/2, ScalePos(25), Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end local closeBtn = vgui.Create("DButton", promoFrame) closeBtn:SetPos(promoFrame:GetWide() - ScaleSize(35), ScalePos(5)) closeBtn:SetSize(ScaleSize(30), ScaleSize(30)) closeBtn:SetText("✕") closeBtn:SetFont("F4Menu_Category") closeBtn:SetTextColor(Color(255, 255, 255)) closeBtn.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, s:IsHovered() and Color(200, 50, 50) or Color(0, 0, 0, 0)) end closeBtn.DoClick = function() promoFrame:Close() end local infoLabel = vgui.Create("DLabel", promoFrame) infoLabel:SetPos(ScalePos(20), ScalePos(60)) infoLabel:SetSize(promoFrame:GetWide() - ScaleSize(40), ScaleSize(25)) infoLabel:SetFont("F4Menu_Item") infoLabel:SetTextColor(Color(200, 200, 200)) infoLabel:SetText("Введите промокод для получения бонусной валюты:") infoLabel:SetContentAlignment(5) local promoEntry = vgui.Create("DTextEntry", promoFrame) promoEntry:SetPos(ScalePos(50), ScalePos(95)) promoEntry:SetSize(promoFrame:GetWide() - ScaleSize(100), ScaleSize(40)) promoEntry:SetFont("F4Menu_Category") promoEntry:SetPlaceholderText("ВВЕДИТЕ КОД") promoEntry.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, Color(35, 35, 38)) s:DrawTextEntryText(Color(255, 255, 255), Color(100, 150, 255), Color(255, 255, 255)) end local activateBtn = vgui.Create("DButton", promoFrame) activateBtn:SetPos(ScalePos(50), ScalePos(145)) activateBtn:SetSize(promoFrame:GetWide() - ScaleSize(100), ScaleSize(40)) activateBtn:SetText("") activateBtn.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, s:IsHovered() and Color(1, 87, 39) or Color(1, 67, 29)) draw.SimpleText("АКТИВИРОВАТЬ", "F4Menu_Category", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end activateBtn.DoClick = function() local code = promoEntry:GetValue() if code ~= "" then local promoPlugin = ix.plugin.Get("promocodes") if promoPlugin then promoPlugin:ActivatePromoCode(code) promoFrame:Close() else LocalPlayer():Notify("Система промокодов недоступна") end else LocalPlayer():Notify("Введите промокод") end end end -- Categories horizontal tabs local categoriesPanel = self.contentPanel:Add("DPanel") categoriesPanel:SetPos(ScalePos(12), ScalePos(135)) categoriesPanel:SetSize(ScaleSize(1475), ScaleSize(60)) categoriesPanel.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, Color(18, 18, 20)) surface.SetDrawColor(30, 30, 35) surface.DrawOutlinedRect(0, 0, w, h, 1) end local tabWidth = math.floor(ScaleSize(1475) / math.max(#catalog, 1)) for i, category in ipairs(catalog) do local btn = vgui.Create("DButton", categoriesPanel) btn:SetPos((i - 1) * tabWidth, 0) btn:SetSize(tabWidth, ScaleSize(60)) btn:SetText("") btn:SetCursor("hand") btn.hoverAnim = 0 btn.Think = function(s) local target = s:IsHovered() and 1 or 0 s.hoverAnim = Lerp(FrameTime() * 10, s.hoverAnim, target) end btn.Paint = function(s, w, h) local isActive = self.activeDonateCategory == category.id if isActive then draw.RoundedBox(6, 5, 5, w - 10, h - 10, darkGreen) surface.SetDrawColor(primaryGreen) surface.DrawRect(5, h - 7, w - 10, 3) elseif s.hoverAnim > 0.01 then local alpha = math.floor(s.hoverAnim * 100) draw.RoundedBox(6, 5, 5, w - 10, h - 10, Color(25, 25, 28, alpha)) end local textColor = isActive and primaryGreen or Color(180, 180, 180) local itemCount = istable(category.items) and #category.items or 0 draw.SimpleText(category.name or "Категория", "F4Menu_Category", w / 2, h / 2 - 5, textColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) draw.SimpleText(itemCount .. " товаров", "F4Menu_InfoSmall", w / 2, h / 2 + 15, Color(120, 120, 120), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end btn.DoClick = function() if self.activeDonateCategory == category.id then return end surface.PlaySound("ui/buttonclickrelease.wav") self.activeDonateCategory = category.id if BuildProducts then BuildProducts() end end end local itemsScroll = vgui.Create("DScrollPanel", self.contentPanel) itemsScroll:SetPos(ScalePos(12), ScalePos(210)) itemsScroll:SetSize(ScaleSize(1475), ScaleSize(530)) itemsScroll.Paint = nil local vbar = itemsScroll:GetVBar() function vbar:Paint(w, h) draw.RoundedBox(8, 3, 0, w - 6, h, Color(15, 15, 17)) end function vbar.btnUp:Paint(w, h) end function vbar.btnDown:Paint(w, h) end function vbar.btnGrip:Paint(w, h) draw.RoundedBox(8, 3, 0, w - 6, h, primaryGreen) end BuildProducts = function() if not IsValid(itemsScroll) then return end itemsScroll:Clear() local category = self:GetDonateCategory(self.activeDonateCategory) or catalog[1] if not category then return end if not istable(category.items) or #category.items == 0 then local emptyPanel = vgui.Create("DPanel", itemsScroll) emptyPanel:SetPos(0, ScalePos(100)) emptyPanel:SetSize(ScaleSize(1475), ScaleSize(200)) emptyPanel.Paint = function(s, w, h) draw.RoundedBox(12, 0, 0, w, h, Color(18, 18, 20)) surface.SetDrawColor(40, 40, 45) surface.DrawOutlinedRect(0, 0, w, h, 2) draw.SimpleText("", "F4Menu_DonateHero", w / 2, h / 2 - 30, Color(80, 80, 85), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) draw.SimpleText("В этой категории пока нет товаров", "F4Menu_Category", w / 2, h / 2 + 20, Color(120, 120, 125), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end return end local columns = 3 local cardWidth = math.floor((ScaleSize(1475) - ScaleSize(30) * (columns - 1)) / columns) local currentX = 0 local currentY = 0 local rowHeight = 0 for idx, entry in ipairs(category.items) do local perksList = istable(entry.perks) and entry.perks or {} local showPerks = (#perksList > 0) and perksList or { "Без дополнительных преимуществ" } local baseHeight = ScaleSize(220) local perkHeight = #showPerks * ScaleSize(20) + ScaleSize(30) local cardHeight = baseHeight + perkHeight local card = vgui.Create("DPanel", itemsScroll) card:SetPos(currentX, currentY) card:SetSize(cardWidth, cardHeight + ScaleSize(10)) card.hoverAnim = 0 card.glowAnim = 0 card.Think = function(s) local hoverTarget = s:IsHovered() and 1 or 0 s.hoverAnim = Lerp(FrameTime() * 8, s.hoverAnim, hoverTarget) s.glowAnim = (s.glowAnim + FrameTime() * 2) % (math.pi * 2) end local accent = ResolveColor(entry.accent or category.accent, 255) local price1Month = entry.price1Month or entry.price or 0 local price3Month = entry.price3Month local hasTimedPurchase = (entry.price1Month ~= nil or entry.price3Month ~= nil) -- Проверяем есть ли временные опции card.selectedPrice = price1Month card.selectedMode = "1month" card.Paint = function(s, w, h) -- Compensate for lift effect local actualHeight = h - ScaleSize(10) -- Card shadow if s.hoverAnim > 0.01 then local shadowOffset = math.floor(s.hoverAnim * 8) draw.RoundedBox(12, shadowOffset / 2, shadowOffset / 2 + ScaleSize(5), w, actualHeight, Color(0, 0, 0, 50 * s.hoverAnim)) end -- Main card local liftY = math.floor(s.hoverAnim * -5) + ScaleSize(5) draw.RoundedBox(12, 0, liftY, w, actualHeight, cardBg) -- Border glow on hover if s.hoverAnim > 0.01 then local glowAlpha = math.floor(50 + 30 * math.sin(s.glowAnim)) surface.SetDrawColor(accent.r, accent.g, accent.b, glowAlpha * s.hoverAnim) surface.DrawOutlinedRect(0, liftY, w, actualHeight, 2) end -- Title draw.SimpleText(entry.title or "Пакет", "F4Menu_DonateTitle", 15, liftY + ScaleSize(55), color_white) -- Price local priceText = FormatDonatePrice(card.selectedPrice, entry.currency or "RUB") draw.SimpleText(priceText, "F4Menu_DonatePrice", w - 15, liftY + ScaleSize(55), primaryGreen, TEXT_ALIGN_RIGHT) -- Period selector background (только если есть временные опции) if hasTimedPurchase then draw.RoundedBox(6, 15, liftY + ScaleSize(95), w - 30, ScaleSize(35), Color(15, 15, 17)) end -- Separator local separatorY = hasTimedPurchase and ScaleSize(145) or ScaleSize(100) surface.SetDrawColor(40, 40, 45) surface.DrawRect(15, liftY + separatorY, w - 30, 1) -- Perks title local perksY = hasTimedPurchase and ScaleSize(160) or ScaleSize(115) draw.SimpleText("Преимущества:", "F4Menu_Category", 15, liftY + perksY, Color(180, 180, 185)) -- Perks list local perkY = liftY + (hasTimedPurchase and ScaleSize(185) or ScaleSize(140)) for _, perk in ipairs(showPerks) do draw.SimpleText("• " .. perk, "F4Menu_Item", 20, perkY, Color(200, 200, 205)) perkY = perkY + ScaleSize(20) end end -- Period selector buttons (только если есть временные опции) if hasTimedPurchase and price1Month then local month1Btn = vgui.Create("DButton", card) month1Btn:SetPos(20, ScaleSize(100)) month1Btn:SetSize((cardWidth - 55) / 2, ScaleSize(25)) month1Btn:SetText("") month1Btn:SetCursor("hand") month1Btn.selected = true month1Btn.Paint = function(s, w, h) local bg = s.selected and primaryGreen or Color(25, 25, 28) local textCol = s.selected and color_white or Color(150, 150, 150) draw.RoundedBox(4, 0, 0, w, h, bg) draw.SimpleText("1 месяц", "F4Menu_Item", w / 2, h / 2, textCol, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end month1Btn.DoClick = function() if month1Btn.selected then return end surface.PlaySound("ui/buttonclick.wav") month1Btn.selected = true if card.month3Btn then card.month3Btn.selected = false end card.selectedPrice = price1Month card.selectedMode = "1month" end card.month1Btn = month1Btn end if hasTimedPurchase and price3Month then local month3Btn = vgui.Create("DButton", card) month3Btn:SetPos(25 + (cardWidth - 55) / 2, ScaleSize(100)) month3Btn:SetSize((cardWidth - 55) / 2, ScaleSize(25)) month3Btn:SetText("") month3Btn:SetCursor("hand") month3Btn.selected = false month3Btn.Paint = function(s, w, h) local bg = s.selected and primaryGreen or Color(25, 25, 28) local textCol = s.selected and color_white or Color(150, 150, 150) draw.RoundedBox(4, 0, 0, w, h, bg) draw.SimpleText("3 месяца", "F4Menu_Item", w / 2, h / 2, textCol, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end month3Btn.DoClick = function() if month3Btn.selected then return end surface.PlaySound("ui/buttonclick.wav") month3Btn.selected = true if card.month1Btn then card.month1Btn.selected = false end card.selectedPrice = price3Month card.selectedMode = "3month" end card.month3Btn = month3Btn end -- Buy button local buyBtn = vgui.Create("DButton", card) buyBtn:SetPos(15, cardHeight - ScaleSize(50)) buyBtn:SetSize(cardWidth - 30, ScaleSize(38)) buyBtn:SetText("") buyBtn:SetCursor("hand") buyBtn.hoverAnim = 0 buyBtn.Think = function(s) local target = s:IsHovered() and 1 or 0 s.hoverAnim = Lerp(FrameTime() * 12, s.hoverAnim, target) end buyBtn.Paint = function(s, w, h) local bgColor = Color( primaryGreen.r + (accent.r - primaryGreen.r) * s.hoverAnim, primaryGreen.g + (accent.g - primaryGreen.g) * s.hoverAnim, primaryGreen.b + (accent.b - primaryGreen.b) * s.hoverAnim ) draw.RoundedBox(6, 0, 0, w, h, bgColor) draw.SimpleText("КУПИТЬ", "F4Menu_DonateBold", w / 2, h / 2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end buyBtn.DoClick = function() surface.PlaySound("ui/buttonclickrelease.wav") self:ConfirmDonatePurchase(entry, card.selectedPrice, card.selectedMode) end rowHeight = math.max(rowHeight, cardHeight + ScaleSize(10)) currentX = currentX + cardWidth + ScaleSize(30) if (idx % columns) == 0 then currentX = 0 currentY = currentY + rowHeight + ScaleSize(25) rowHeight = 0 end end end BuildProducts() timer.Simple(0.1, function() if BuildProducts and IsValid(itemsScroll) then BuildProducts() end end) end function PLUGIN:CreateRulesTab() if IsValid(self.contentPanel) then self.contentPanel:Remove() end local panelW, panelH = ScaleSize(1500), ScaleSize(750) local panelX, panelY = ScalePos(210), ScalePos(165) self.contentPanel = vgui.Create("DPanel", self.f4Menu) self.contentPanel:SetSize(panelW, panelH) self.contentPanel:SetPos(panelX, panelY) self.contentPanel.Paint = function(s, w, h) draw.RoundedBox(15, 0, 0, w, h, Color(13, 13, 13, 218)) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) end local html = vgui.Create("DHTML", self.contentPanel) html:SetSize(panelW - ScaleSize(20), panelH - ScaleSize(20)) html:SetPos(ScalePos(10), ScalePos(10)) html:OpenURL("https://sites.google.com/view/frontteamsite/правила") end --[[ СИСТЕМА ЖАЛОБ УДАЛЕНА function PLUGIN:CreateContactsTab() local panelW, panelH = ScaleSize(1500), ScaleSize(750) local panelX, panelY = ScalePos(210), ScalePos(165) local contactsPanel = vgui.Create("DPanel", self.f4Menu) contactsPanel:SetSize(panelW, panelH) contactsPanel:SetPos(panelX, panelY) contactsPanel.Paint = function(s, w, h) draw.RoundedBox(15, 0, 0, w, h, Color(13, 13, 13, 218)) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) end self.contentPanel = contactsPanel -- Левая колонка: список админов local adminListPanel = vgui.Create("DScrollPanel", contactsPanel) adminListPanel:SetSize(ScaleSize(680), ScaleSize(590)) adminListPanel:SetPos(ScalePos(34), ScalePos(126)) adminListPanel.Paint = function(s, w, h) draw.RoundedBox(10, 0, 0, w, h, Color(21, 21, 21, 255)) end -- Стилизуем скроллбар local bar = adminListPanel:GetVBar() function bar:Paint(w, h) end function bar.btnUp:Paint(w, h) end function bar.btnDown:Paint(w, h) end function bar.btnGrip:Paint(w, h) draw.RoundedBox(5, 0, 0, w, h, Color(1, 67, 29, 180)) end local adminTitle = vgui.Create("DLabel", contactsPanel) adminTitle:SetFont("F4Menu_Title") adminTitle:SetText("СПИСОК АДМИНИСТРАТОРОВ") adminTitle:SetTextColor(color_white) adminTitle:SetPos(ScalePos(230), ScalePos(18)) adminTitle:SetSize(ScaleSize(628), ScaleSize(54)) adminTitle:SetContentAlignment(4) adminTitle:SetFont("F4Menu_Title") -- Список админов (живые игроки с привилегией не 'user') local admins = {} for _, ply in ipairs(player.GetAll()) do if ply:GetUserGroup() and ply:GetUserGroup() ~= "user" then table.insert(admins, ply) end end local startY = ScalePos(40) for i, ply in ipairs(admins) do local row = vgui.Create("DPanel", adminListPanel) row:SetSize(ScaleSize(620), ScaleSize(55)) row:Dock(TOP) -- Для первого row делаем больший верхний отступ if i == 1 then row:DockMargin(ScaleSize(20), ScaleSize(15), ScaleSize(20), ScaleSize(10)) else row:DockMargin(ScaleSize(20), 0, ScaleSize(20), ScaleSize(10)) end row.Paint = function(s, w, h) draw.RoundedBox(7, 0, 0, w, h, Color(29, 28, 28, 255)) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) end -- Аватар Steam local avatar = vgui.Create("AvatarImage", row) avatar:SetSize(ScaleSize(40), ScaleSize(40)) avatar:SetPos(ScalePos(10), ScalePos(7)) avatar:SetPlayer(ply, 64) -- Имя local name = vgui.Create("DLabel", row) name:SetFont("F4Menu_RulesTitle") name:SetText(ply:Nick()) name:SetTextColor(color_white) name:SetPos(ScalePos(61), ScalePos(10)) name:SetSize(ScaleSize(200), ScaleSize(34)) -- Должность local rank = vgui.Create("DLabel", row) rank:SetFont("F4Menu_RulesTitle") rank:SetText(ply:GetUserGroup()) rank:SetTextColor(color_white) rank:SetPos(ScalePos(450), ScalePos(10)) rank:SetSize(ScaleSize(155), ScaleSize(34)) end -- Правая колонка: связь с администрацией local callTitle = vgui.Create("DLabel", contactsPanel) callTitle:SetFont("F4Menu_Title") callTitle:SetText("ПОЗВАТЬ АДМИНИСТРАТОРА") callTitle:SetTextColor(color_white) callTitle:SetPos(ScalePos(980), ScalePos(18)) callTitle:SetSize(ScaleSize(620), ScaleSize(54)) callTitle:SetContentAlignment(4) local callDesc = vgui.Create("DLabel", contactsPanel) callDesc:SetFont("F4Menu_Item") callDesc:SetText("Выберите категорию чтобы связаться с Администрацией") callDesc:SetTextColor(color_white) callDesc:SetPos(ScalePos(850), ScalePos(150)) callDesc:SetSize(ScaleSize(575), ScaleSize(58)) callDesc:SetContentAlignment(5) -- Кнопки категорий -- Кнопки категорий local btnComplaint, btnHelp local mode = "complaint" -- "complaint" или "help" btnComplaint = vgui.Create("DButton", contactsPanel) btnComplaint:SetSize(ScaleSize(125), ScaleSize(35)) btnComplaint:SetPos(ScalePos(900) + ScalePos(92), ScalePos(200)) btnComplaint:SetText("") btnComplaint.Paint = function(s, w, h) if mode == "complaint" then draw.RoundedBox(7, 0, 0, w, h, Color(1, 67, 29, 255)) else draw.RoundedBox(7, 0, 0, w, h, Color(29, 28, 28, 255)) end surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) draw.SimpleText("Жалоба", "F4Menu_RulesTitle", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end btnComplaint.DoClick = function() mode = "complaint" updatePanels() end btnHelp = vgui.Create("DButton", contactsPanel) btnHelp:SetSize(ScaleSize(125), ScaleSize(35)) btnHelp:SetPos(ScalePos(1040) + ScalePos(92), ScalePos(200)) btnHelp:SetText("") btnHelp.Paint = function(s, w, h) if mode == "help" then draw.RoundedBox(7, 0, 0, w, h, Color(1, 67, 29, 255)) else draw.RoundedBox(7, 0, 0, w, h, Color(29, 28, 28, 255)) end surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) draw.SimpleText("Помощь", "F4Menu_RulesTitle", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end btnHelp.DoClick = function() mode = "help" updatePanels() end local reasonPanel = vgui.Create("DPanel", contactsPanel) reasonPanel:SetSize(ScaleSize(250), ScaleSize(100)) reasonPanel:SetPos(ScalePos(850), ScalePos(250)) reasonPanel.Paint = function() end local reasonEntry = vgui.Create("DTextEntry", reasonPanel) reasonEntry:SetFont("F4Menu_Item") reasonEntry:SetTextColor(Color(194, 194, 194)) reasonEntry:SetPos(0, 0) reasonEntry:SetSize(ScaleSize(250), ScaleSize(100)) reasonEntry:SetMultiline(true) reasonEntry:SetVerticalScrollbarEnabled(false) reasonEntry:SetDrawBorder(false) reasonEntry:SetDrawBackground(false) reasonEntry.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, Color(29, 28, 28, 255)) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) -- Показываем плейсхолдер в зависимости от режима if s:GetValue() == "" then local placeholder = mode == "complaint" and "Введите причину жалобы..." or "Введите ваш вопрос администраторам..." draw.SimpleText(placeholder, "F4Menu_Item", 5, 5, Color(100, 100, 100)) end s:DrawTextEntryText(Color(194, 194, 194), Color(1, 67, 29), Color(194, 194, 194)) end -- Панель нарушителей (как раньше) local offendersPanel = vgui.Create("DPanel", contactsPanel) offendersPanel:SetSize(ScaleSize(315), ScaleSize(360)) offendersPanel:SetPos(ScalePos(1150), ScalePos(250)) offendersPanel.Paint = function(s, w, h) draw.RoundedBox(7, 0, 0, w, h, Color(29, 28, 28, 255)) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) end local searchEntry = vgui.Create("DTextEntry", offendersPanel) searchEntry:SetSize(ScaleSize(285), ScaleSize(38)) searchEntry:SetPos(ScalePos(15), ScalePos(10)) searchEntry:SetFont("F4Menu_Item") searchEntry:SetPlaceholderText("Поиск игрока по нику...") searchEntry:SetTextColor(Color(194, 194, 194)) searchEntry.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, Color(29, 28, 28, 255)) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) s:DrawTextEntryText(Color(194, 194, 194), Color(1, 67, 29), Color(194, 194, 194)) end local offendersScroll = vgui.Create("DScrollPanel", offendersPanel) local function layoutOffendersScroll() local scrollY = ScalePos(10) + ScaleSize(38) + ScalePos(2) local scrollH = offendersPanel:GetTall() - scrollY - ScalePos(10) offendersScroll:SetPos(ScalePos(15), scrollY) offendersScroll:SetSize(ScaleSize(285), scrollH) end offendersPanel.PerformLayout = layoutOffendersScroll layoutOffendersScroll() local vbar = offendersScroll:GetVBar() function vbar:Paint(w, h) end function vbar.btnUp:Paint(w, h) end function vbar.btnDown:Paint(w, h) end function vbar.btnGrip:Paint(w, h) draw.RoundedBox(5, 0, 0, w, h, Color(1, 67, 29, 180)) end local selectedOffender = nil local function UpdateOffendersList(filter) offendersScroll:Clear() local players = player.GetAll() for _, ply in ipairs(players) do if not filter or string.find(string.lower(ply:Nick()), string.lower(filter), 1, true) then local row = vgui.Create("DButton", offendersScroll) row:SetSize(ScaleSize(265), ScaleSize(38)) row:Dock(TOP) row:DockMargin(0, 0, 0, 7) row:SetText("") row.Paint = function(s, w, h) if selectedOffender == ply then draw.RoundedBox(6, 0, 0, w, h, Color(1, 67, 29, 220)) else draw.RoundedBox(6, 0, 0, w, h, Color(21, 21, 21, 230)) end surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) end row.DoClick = function() selectedOffender = ply UpdateOffendersList(searchEntry:GetValue()) end local avatar = vgui.Create("AvatarImage", row) avatar:SetSize(ScaleSize(28), ScaleSize(28)) avatar:SetPos(ScalePos(6), ScalePos(5)) avatar:SetPlayer(ply, 64) local name = vgui.Create("DLabel", row) name:SetFont("F4Menu_Item") name:SetText(ply:Nick()) name:SetTextColor(color_white) name:SetPos(ScalePos(42), ScalePos(8)) name:SetSize(ScaleSize(200), ScaleSize(22)) end end end UpdateOffendersList("") searchEntry.OnChange = function(self) UpdateOffendersList(self:GetValue()) end -- Кнопка отправить local btnSend = vgui.Create("DButton", contactsPanel) btnSend:SetSize(ScaleSize(120), ScaleSize(38)) btnSend:SetText("") btnSend.Paint = function(s, w, h) draw.RoundedBox(7, 0, 0, w, h, Color(1, 67, 29)) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) draw.SimpleText("Отправить", "F4Menu_RulesTitle", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end btnSend.DoClick = function() if mode == "complaint" then -- Отправка жалобы через complaints-плагин local targetName = selectedOffender and selectedOffender:Nick() or "" local reason = reasonEntry:GetValue() or "" if not selectedOffender then LocalPlayer():Notify("Выберите нарушителя из списка!") return end if reason == "" then LocalPlayer():Notify("Укажите причину жалобы!") return end if #reason < 5 then LocalPlayer():Notify("Причина жалобы должна содержать минимум 5 символов!") return end net.Start("ix.SubmitComplaint") net.WriteString(targetName) net.WriteString(reason) net.SendToServer() reasonEntry:SetValue("") selectedOffender = nil UpdateOffendersList("") elseif mode == "help" then -- Отправка вопроса администраторам local question = reasonEntry:GetValue() or "" if question == "" then LocalPlayer():Notify("Укажите ваш вопрос!") return end if #question < 10 then LocalPlayer():Notify("Вопрос должен содержать минимум 10 символов!") return end -- Отправляем вопрос в чат администраторов net.Start("ix.SendHelpRequest") net.WriteString(question) net.SendToServer() LocalPlayer():Notify("Ваш вопрос отправлен администраторам!") reasonEntry:SetValue("") end end -- Если игрок админ — добавить кнопку просмотра жалоб timer.Simple(0, function() if not IsValid(contactsPanel) then return end local complaintsPlugin = ix and ix.plugins and ix.plugins.Get("complaints") if complaintsPlugin and complaintsPlugin.CanSeeComplaints and complaintsPlugin:CanSeeComplaints(LocalPlayer()) then local btnViewComplaints = vgui.Create("DButton", contactsPanel) btnViewComplaints:SetSize(ScaleSize(180), ScaleSize(36)) btnViewComplaints:SetPos(ScalePos(900), ScalePos(650)) btnViewComplaints:SetText("Просмотреть жалобы игроков") btnViewComplaints:SetFont("F4Menu_Item") btnViewComplaints.Paint = function(s, w, h) draw.RoundedBox(7, 0, 0, w, h, Color(1, 67, 29, 255)) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) end btnViewComplaints.DoClick = function() complaintsPlugin:RequestComplaints() timer.Simple(0.1, function() complaintsPlugin:ShowComplaintsPanel() end) end end end) -- Функция обновления видимости панелей function updatePanels() if mode == "complaint" then offendersPanel:SetVisible(true) reasonPanel:SetVisible(true) btnSend:SetVisible(true) -- Слева, как обычно reasonPanel:SetPos(ScalePos(850), ScalePos(250)) btnSend:SetPos(ScalePos(900) + ScalePos(20), ScalePos(370)) elseif mode == "help" then offendersPanel:SetVisible(false) reasonPanel:SetVisible(true) btnSend:SetVisible(true) -- Центрируем по правой стороне (там где offendersPanel) local rightX = ScalePos(970) local rightW = ScaleSize(315) local panelW = reasonPanel:GetWide() local panelX = rightX + math.floor((rightW - panelW) / 2) reasonPanel:SetPos(panelX, ScalePos(250)) -- Кнопка под причиной local btnX = panelX + math.floor((panelW - btnSend:GetWide()) / 2) + ScalePos(8) btnSend:SetPos(btnX, ScalePos(250) + reasonPanel:GetTall() + ScalePos(15)) else offendersPanel:SetVisible(false) reasonPanel:SetVisible(false) btnSend:SetVisible(false) end end -- Скрыть всё по умолчанию (и выбрать жалобу) updatePanels() end --]] function PLUGIN:CreateThanksTab() if IsValid(self.contentPanel) then self.contentPanel:Remove() end local panelW, panelH = ScaleSize(1500), ScaleSize(750) local panelX, panelY = ScalePos(210), ScalePos(165) self.contentPanel = vgui.Create("DPanel", self.f4Menu) self.contentPanel:SetSize(panelW, panelH) self.contentPanel:SetPos(panelX, panelY) self.contentPanel.Paint = function(s, w, h) draw.RoundedBox(15, 0, 0, w, h, Color(13, 13, 13, 218)) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) end local pages = self:GetThanksPages() if not pages or #pages == 0 then local noData = vgui.Create("DLabel", self.contentPanel) noData:SetFont("F4Menu_Category") noData:SetText("Нет доступных страниц благодарностей") noData:SetTextColor(Color(150, 150, 150)) noData:SetPos(0, panelH / 2 - 20) noData:SetSize(panelW, 40) noData:SetContentAlignment(5) return end -- Текущая страница self.currentThanksPage = self.currentThanksPage or 1 self.currentThanksPage = math.Clamp(self.currentThanksPage, 1, #pages) local currentPage = pages[self.currentThanksPage] -- Заголовок страницы local title = vgui.Create("DLabel", self.contentPanel) title:SetFont("F4Menu_Title") title:SetText(currentPage.title or "БЛАГОДАРНОСТИ") title:SetTextColor(color_white) title:SetPos(0, ScalePos(20)) title:SetSize(panelW, ScaleSize(40)) title:SetContentAlignment(5) -- Кнопки переключения страниц if #pages > 1 then -- Кнопка назад local prevBtn = vgui.Create("DButton", self.contentPanel) prevBtn:SetPos(ScalePos(30), ScalePos(25)) prevBtn:SetSize(ScaleSize(40), ScaleSize(40)) prevBtn:SetText("") prevBtn.DoClick = function() self.currentThanksPage = self.currentThanksPage - 1 if self.currentThanksPage < 1 then self.currentThanksPage = #pages end self:CreateThanksTab() end prevBtn.Paint = function(s, w, h) local col = s:IsHovered() and Color(56, 102, 35) or Color(27, 94, 32) draw.RoundedBox(8, 0, 0, w, h, col) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) draw.SimpleText("<", "F4Menu_Category", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end -- Кнопка вперед local nextBtn = vgui.Create("DButton", self.contentPanel) nextBtn:SetPos(panelW - ScalePos(70), ScalePos(25)) nextBtn:SetSize(ScaleSize(40), ScaleSize(40)) nextBtn:SetText("") nextBtn.DoClick = function() self.currentThanksPage = self.currentThanksPage + 1 if self.currentThanksPage > #pages then self.currentThanksPage = 1 end self:CreateThanksTab() end nextBtn.Paint = function(s, w, h) local col = s:IsHovered() and Color(56, 102, 35) or Color(27, 94, 32) draw.RoundedBox(8, 0, 0, w, h, col) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) draw.SimpleText(">", "F4Menu_Category", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end -- Индикатор страниц local pageIndicator = vgui.Create("DLabel", self.contentPanel) pageIndicator:SetFont("F4Menu_Item") pageIndicator:SetText(self.currentThanksPage .. " / " .. #pages) pageIndicator:SetTextColor(Color(165, 214, 167)) pageIndicator:SetPos(0, ScalePos(70)) pageIndicator:SetSize(panelW, ScaleSize(20)) pageIndicator:SetContentAlignment(5) end -- Контейнер для моделей local modelsContainer = vgui.Create("DPanel", self.contentPanel) modelsContainer:SetPos(ScalePos(50), ScalePos(120)) modelsContainer:SetSize(panelW - ScaleSize(100), panelH - ScaleSize(140)) modelsContainer.Paint = function() end local people = currentPage.people or {} local peopleCount = math.min(#people, 10) if peopleCount == 0 then local noPeople = vgui.Create("DLabel", modelsContainer) noPeople:SetFont("F4Menu_Item") noPeople:SetText("Нет людей на этой странице") noPeople:SetTextColor(Color(150, 150, 150)) noPeople:SetPos(0, 0) noPeople:SetSize(modelsContainer:GetWide(), modelsContainer:GetTall()) noPeople:SetContentAlignment(5) return end -- Расчет размеров для моделей local containerWidth = modelsContainer:GetWide() local containerHeight = modelsContainer:GetTall() local modelWidth = ScaleSize(140) -- Фиксированная ширина для каждой модели local modelHeight = containerHeight - ScaleSize(40) -- Оставляем место для имени -- Расчет общей ширины всех моделей и отступа для центрирования local totalWidth = peopleCount * modelWidth local startX = (containerWidth - totalWidth) / 2 -- Создаем модели for i, person in ipairs(people) do if i > 10 then break end -- Позиция с учетом центрирования local xPos = startX + (i - 1) * modelWidth -- Контейнер для модели и имени local personContainer = vgui.Create("DButton", modelsContainer) personContainer:SetPos(xPos, 0) personContainer:SetSize(modelWidth, containerHeight) personContainer:SetText("") personContainer.DoClick = function() self:OpenPersonInfoDialog(person) end personContainer.Paint = function(s, w, h) if s:IsHovered() then surface.SetDrawColor(Color(27, 94, 32, 50)) surface.DrawRect(0, 0, w, h) end end -- DModelPanel для модели local modelPanel = vgui.Create("DModelPanel", personContainer) modelPanel:SetPos(ScaleSize(5), 0) modelPanel:SetSize(modelWidth - ScaleSize(10), modelHeight) modelPanel:SetModel(person.model or "models/player/group01/male_01.mdl") modelPanel:SetFOV(35) modelPanel:SetMouseInputEnabled(false) -- Настройка камеры для отображения модели в полный рост local ent = modelPanel.Entity if IsValid(ent) then local mins, maxs = ent:GetRenderBounds() local modelHeight = maxs.z - mins.z local modelCenter = (mins + maxs) / 2 modelPanel:SetLookAt(modelCenter) modelPanel:SetCamPos(modelCenter + Vector(modelHeight * 0.8, 0, modelHeight * 0.1)) -- Устанавливаем анимацию if person.sequence then local seq = ent:LookupSequence(person.sequence) if seq > 0 then ent:ResetSequence(seq) end end end modelPanel.LayoutEntity = function(pnl, ent) -- Отключаем стандартную анимацию вращения end -- Имя под моделью local nameLabel = vgui.Create("DLabel", personContainer) nameLabel:SetPos(0, modelHeight + ScaleSize(5)) nameLabel:SetSize(modelWidth, ScaleSize(35)) nameLabel:SetFont("F4Menu_Item") nameLabel:SetText(person.name or "Неизвестно") nameLabel:SetTextColor(Color(165, 214, 167)) nameLabel:SetContentAlignment(5) nameLabel:SetMouseInputEnabled(false) end end function PLUGIN:OpenPersonInfoDialog(person) if not person then return end local frame = vgui.Create("DFrame") frame:SetSize(ScaleSize(600), ScaleSize(400)) frame:Center() frame:SetTitle("") frame:SetDraggable(true) frame:ShowCloseButton(false) frame:MakePopup() frame.Paint = function(s, w, h) draw.RoundedBox(18, 0, 0, w, h, Color(13, 13, 13, 240)) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 2) end -- Модель слева local modelPanel = vgui.Create("DModelPanel", frame) modelPanel:SetPos(ScalePos(20), ScalePos(20)) modelPanel:SetSize(ScaleSize(250), ScaleSize(360)) modelPanel:SetModel(person.model or "models/player/group01/male_01.mdl") modelPanel:SetFOV(50) local ent = modelPanel.Entity if IsValid(ent) then local mins, maxs = ent:GetRenderBounds() local modelHeight = maxs.z - mins.z local modelCenter = (mins + maxs) / 2 -- Центр смотрит на середину модели по высоте modelPanel:SetLookAt(Vector(modelCenter.x, modelCenter.y, mins.z + modelHeight * 0.4)) -- Камера отодвинута достаточно далеко чтобы показать всю модель modelPanel:SetCamPos(modelCenter + Vector(modelHeight * 1.0, 0, 0)) if person.sequence then local seq = ent:LookupSequence(person.sequence) if seq > 0 then ent:ResetSequence(seq) end end end modelPanel.LayoutEntity = function() end -- Информация справа local infoPanel = vgui.Create("DPanel", frame) infoPanel:SetPos(ScalePos(290), ScalePos(20)) infoPanel:SetSize(ScaleSize(290), ScaleSize(310)) infoPanel.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, Color(18, 18, 18)) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) end -- Имя local nameLabel = vgui.Create("DLabel", infoPanel) nameLabel:SetPos(ScalePos(15), ScalePos(15)) nameLabel:SetSize(ScaleSize(260), ScaleSize(30)) nameLabel:SetFont("F4Menu_Category") nameLabel:SetText(person.name or "Неизвестно") nameLabel:SetTextColor(Color(165, 214, 167)) nameLabel:SetContentAlignment(5) -- Разделитель local divider = vgui.Create("DPanel", infoPanel) divider:SetPos(ScalePos(15), ScalePos(50)) divider:SetSize(ScaleSize(260), 1) divider.Paint = function(s, w, h) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawRect(0, 0, w, h) end -- Описание local descLabel = vgui.Create("DLabel", infoPanel) descLabel:SetPos(ScalePos(15), ScalePos(65)) descLabel:SetSize(ScaleSize(260), ScaleSize(230)) descLabel:SetFont("F4Menu_InfoSmall") descLabel:SetText(person.description or "Нет описания") descLabel:SetTextColor(Color(220, 220, 220)) descLabel:SetContentAlignment(7) descLabel:SetWrap(true) descLabel:SetAutoStretchVertical(true) -- Кнопка открытия ссылки if person.link and person.link ~= "" then local linkBtn = vgui.Create("DButton", frame) linkBtn:SetPos(ScalePos(290), ScalePos(345)) linkBtn:SetSize(ScaleSize(140), ScaleSize(35)) linkBtn:SetText("") linkBtn.DoClick = function() gui.OpenURL(person.link) end linkBtn.Paint = function(s, w, h) local col = s:IsHovered() and Color(56, 102, 35) or Color(27, 94, 32) draw.RoundedBox(8, 0, 0, w, h, col) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) draw.SimpleText("ПРОФИЛЬ", "F4Menu_Category", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end end -- Кнопка закрытия local closeBtn = vgui.Create("DButton", frame) closeBtn:SetPos(ScalePos(445), ScalePos(345)) closeBtn:SetSize(ScaleSize(135), ScaleSize(35)) closeBtn:SetText("") closeBtn.DoClick = function() frame:Close() end closeBtn.Paint = function(s, w, h) local col = s:IsHovered() and Color(50, 18, 18) or Color(35, 13, 13) draw.RoundedBox(8, 0, 0, w, h, col) surface.SetDrawColor(Color(67, 1, 1)) surface.DrawOutlinedRect(0, 0, w, h, 1) draw.SimpleText("ЗАКРЫТЬ", "F4Menu_Category", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end end function PLUGIN:CreateSettingsTab() if IsValid(self.contentPanel) then self.contentPanel:Remove() end local panelW, panelH = ScaleSize(1500), ScaleSize(750) local panelX, panelY = ScalePos(210), ScalePos(165) self.contentPanel = vgui.Create("DPanel", self.f4Menu) self.contentPanel:SetSize(panelW, panelH) self.contentPanel:SetPos(panelX, panelY) self.contentPanel.Paint = function(s, w, h) end -- Прозрачный фон -- Скролл панель для категорий local scroll = vgui.Create("DScrollPanel", self.contentPanel) scroll:SetPos(0, 0) scroll:SetSize(panelW, panelH) local sbar = scroll:GetVBar() sbar:SetWide(8) sbar.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, Color(21, 21, 21, 200)) end sbar.btnGrip.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, Color(1, 67, 29, 200)) end sbar.btnUp.Paint = function() end sbar.btnDown.Paint = function() end -- Получаем все настройки по категориям local allOptions = ix.option.GetAllByCategories(true) if not allOptions or table.Count(allOptions) == 0 then local noSettings = vgui.Create("DLabel", scroll) noSettings:SetFont("F4Menu_InfoValue") noSettings:SetText("Настройки не найдены") noSettings:SetTextColor(Color(150, 150, 150)) noSettings:Dock(TOP) noSettings:DockMargin(20, 20, 20, 0) noSettings:SizeToContents() return end -- Создаем категории как отдельные панели for category, options in SortedPairs(allOptions) do local categoryName = L(category) -- Сортируем опции по имени table.sort(options, function(a, b) return L(a.phrase) < L(b.phrase) end) -- Вычисляем высоту категории local headerHeight = ScaleSize(34) local headerMargin = ScaleSize(37) local settingGap = ScaleSize(25) local bottomPadding = ScaleSize(45) local totalSettingsHeight = 0 -- Рассчитываем высоту для каждой настройки for _, data in pairs(options) do if data.type == ix.type.number then totalSettingsHeight = totalSettingsHeight + ScaleSize(56) + settingGap elseif data.type == ix.type.bool then totalSettingsHeight = totalSettingsHeight + ScaleSize(24) + settingGap elseif data.type == ix.type.array or data.type == ix.type.string then totalSettingsHeight = totalSettingsHeight + ScaleSize(34) + settingGap else totalSettingsHeight = totalSettingsHeight + ScaleSize(24) + settingGap end end totalSettingsHeight = totalSettingsHeight - settingGap -- убираем последний gap local categoryHeight = headerHeight + headerMargin + totalSettingsHeight + bottomPadding -- Панель категории (Rectangle 933 из Figma - width: 1265px) local categoryBox = vgui.Create("DPanel", scroll) categoryBox:Dock(TOP) categoryBox:DockMargin(ScalePos(118), 0, ScalePos(118), ScalePos(35)) categoryBox:SetTall(categoryHeight) categoryBox.Paint = function(s, w, h) -- background: #0D0D0D; opacity: 0.85; draw.RoundedBox(15, 0, 0, w, h, ColorAlpha(Color(13, 13, 13), 217)) -- border: 1px solid #01431D; surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) end -- Заголовок категории local catTitle = vgui.Create("DLabel", categoryBox) catTitle:SetPos(ScalePos(25), ScalePos(22)) catTitle:SetFont("F4Menu_InfoHeader") -- font-size: 28px; font-weight: 600; catTitle:SetText(categoryName) catTitle:SetTextColor(color_white) catTitle:SizeToContents() -- Контейнер для настроек (Frame 15) local settingsContainer = vgui.Create("DPanel", categoryBox) settingsContainer:SetPos(ScalePos(27), headerHeight + headerMargin) settingsContainer:SetSize(ScaleSize(1200), totalSettingsHeight) settingsContainer.Paint = function() end -- Создаем настройки local yOffset = 0 for _, data in pairs(options) do local key = data.key local value = ix.util.SanitizeType(data.type, ix.option.Get(key)) -- Название настройки (Group 21/25) local nameLabel = vgui.Create("DLabel", settingsContainer) nameLabel:SetPos(0, yOffset) nameLabel:SetFont("F4Menu_RulesTitle") -- font-size: 20px nameLabel:SetText(L(data.phrase)) nameLabel:SetTextColor(color_white) nameLabel:SizeToContents() -- Создаем контрол в зависимости от типа if data.type == ix.type.number then -- Слайдер (Group 22) local sliderHeight = ScaleSize(56) -- Значение (0.999999) local valueLabel = vgui.Create("DLabel", settingsContainer) valueLabel:SetPos(ScaleSize(1114), yOffset) valueLabel:SetFont("F4Menu_RulesTitle") valueLabel:SetText(tostring(value)) valueLabel:SetTextColor(ColorAlpha(color_white, 165)) -- rgba(255, 255, 255, 0.65) valueLabel:SizeToContents() -- Трек слайдера (Rectangle 18) local trackY = yOffset + ScaleSize(39) local slider = vgui.Create("DPanel", settingsContainer) slider:SetPos(0, trackY) slider:SetSize(ScaleSize(1200), ScaleSize(17)) slider:SetCursor("hand") slider.min = data.min or 0 slider.max = data.max or 100 slider.decimals = data.decimals or 0 slider.value = value slider.dragging = false slider.Paint = function(s, w, h) -- Трек (Rectangle 18) draw.RoundedBox(50, 0, ScaleSize(7), w, ScaleSize(3), ColorAlpha(color_white, 165)) -- Прогресс (Rectangle 20) local progress = (s.value - s.min) / (s.max - s.min) local progressW = w * progress draw.RoundedBox(50, 0, ScaleSize(7), progressW, ScaleSize(3), Color(56, 84, 45)) -- #38542D -- Ручка (Rectangle 19) local knobX = math.Clamp(progressW - ScaleSize(8.5), 0, w - ScaleSize(17)) draw.RoundedBox(20, knobX, 0, ScaleSize(17), ScaleSize(17), Color(56, 84, 45)) end slider.OnMousePressed = function(s) s.dragging = true s:MouseCapture(true) end slider.OnMouseReleased = function(s) s.dragging = false s:MouseCapture(false) end slider.Think = function(s) if s.dragging then local x, _ = s:CursorPos() local fraction = math.Clamp(x / s:GetWide(), 0, 1) local newValue = s.min + fraction * (s.max - s.min) newValue = math.Round(newValue, s.decimals) if newValue != s.value then s.value = newValue valueLabel:SetText(tostring(newValue)) valueLabel:SizeToContents() ix.option.Set(key, newValue) end end end yOffset = yOffset + sliderHeight + settingGap elseif data.type == ix.type.bool then -- Чекбокс (Group 24) local checkW = ScaleSize(34) local checkH = ScaleSize(17) local checkX = ScaleSize(1166) local checkY = yOffset + ScaleSize(4) -- Текст состояния local stateLabel = vgui.Create("DLabel", settingsContainer) stateLabel:SetPos(ScaleSize(1041), yOffset) stateLabel:SetFont("F4Menu_RulesTitle") stateLabel:SetText(value and "Включено" or "Выключено") stateLabel:SetTextColor(ColorAlpha(color_white, 165)) stateLabel:SizeToContents() -- Чекбокс local checkbox = vgui.Create("DButton", settingsContainer) checkbox:SetPos(checkX, checkY) checkbox:SetSize(checkW, checkH) checkbox:SetText("") checkbox.checked = value checkbox.Paint = function(s, w, h) if s.checked then -- Включено (transform: rotate(180deg)) -- Ручка слева draw.RoundedBox(20, 0, 0, ScaleSize(17), ScaleSize(17), Color(1, 67, 29)) -- #01431D -- Фон справа draw.RoundedBoxEx(10, ScaleSize(14), ScaleSize(3), ScaleSize(20), ScaleSize(11), ColorAlpha(Color(56, 84, 45), 64), false, true, false, true) else -- Выключено -- Ручка справа draw.RoundedBox(20, ScaleSize(17), 0, ScaleSize(17), ScaleSize(17), ColorAlpha(color_white, 165)) -- Фон слева draw.RoundedBoxEx(10, 0, ScaleSize(3), ScaleSize(20), ScaleSize(11), ColorAlpha(color_white, 64), true, false, true, false) end end checkbox.DoClick = function(s) s.checked = not s.checked stateLabel:SetText(s.checked and "Включено" or "Выключено") stateLabel:SizeToContents() ix.option.Set(key, s.checked) LocalPlayer():EmitSound("buttons/button15.wav", 75, s.checked and 120 or 100, 0.3) end yOffset = yOffset + ScaleSize(24) + settingGap elseif data.type == ix.type.array then -- Выпадающий список (Frame 25) local comboW = ScaleSize(244) local comboH = ScaleSize(34) local comboX = ScaleSize(955) local comboY = yOffset - ScaleSize(5) local combo = vgui.Create("DComboBox", settingsContainer) combo:SetPos(comboX, comboY) combo:SetSize(comboW, comboH) combo:SetFont("F4Menu_RulesTitle") combo:SetTextColor(color_white) combo.Paint = function(s, w, h) -- background: #1D1C1C; border: 1px solid #01431D; draw.RoundedBox(7, 0, 0, w, h, Color(29, 28, 28)) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) end combo.DropButton.Paint = function(s, w, h) -- Arrow 1 (Stroke) - стрелка вниз surface.SetDrawColor(Color(1, 67, 29)) local cx, cy = w/2, h/2 surface.DrawLine(cx - 4, cy - 2, cx, cy + 2) surface.DrawLine(cx, cy + 2, cx + 4, cy - 2) end if isfunction(data.populate) then local entries = data.populate() for k, v in pairs(entries) do combo:AddChoice(v, k, k == value) end end combo.OnSelect = function(s, index, val, optionData) ix.option.Set(key, optionData) LocalPlayer():EmitSound("buttons/button15.wav", 75, 100, 0.3) end yOffset = yOffset + comboH + settingGap elseif data.type == ix.type.string then -- Текстовое поле local entryW = ScaleSize(244) local entryH = ScaleSize(34) local entryX = ScaleSize(955) local entryY = yOffset - ScaleSize(5) local entry = vgui.Create("DTextEntry", settingsContainer) entry:SetPos(entryX, entryY) entry:SetSize(entryW, entryH) entry:SetFont("F4Menu_RulesTitle") entry:SetValue(value) entry:SetTextColor(color_white) entry.Paint = function(s, w, h) draw.RoundedBox(7, 0, 0, w, h, Color(29, 28, 28)) surface.SetDrawColor(Color(1, 67, 29)) surface.DrawOutlinedRect(0, 0, w, h, 1) s:DrawTextEntryText(color_white, Color(1, 67, 29), color_white) end entry.OnEnter = function(s) ix.option.Set(key, s:GetValue()) LocalPlayer():EmitSound("buttons/button15.wav", 75, 100, 0.3) end yOffset = yOffset + entryH + settingGap else -- Остальные типы (по умолчанию как текст) yOffset = yOffset + ScaleSize(24) + settingGap end end end end function PLUGIN:CreateSquadsTab() if not IsValid(self.contentPanel) then return end self.contentPanel:Clear() local squadsPlugin = ix.plugin.Get("squads") if not squadsPlugin then local errorLabel = vgui.Create("DLabel", self.contentPanel) errorLabel:SetPos(20, 20) errorLabel:SetFont("F4Menu_Category") errorLabel:SetTextColor(Color(255, 100, 100)) errorLabel:SetText("Система отрядов не загружена") errorLabel:SizeToContents() return end -- Безопасное получение данных отряда local squad = squadsPlugin.currentSquad local isInSquad = squad ~= nil local isLeader = false if squad and squad.leader then isLeader = (squad.leader == LocalPlayer():SteamID()) end -- Заголовок local headerPanel = vgui.Create("DPanel", self.contentPanel) headerPanel:SetPos(ScalePos(12), ScalePos(12)) headerPanel:SetSize(ScaleSize(1475), ScaleSize(60)) headerPanel.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, Color(18, 18, 20)) surface.SetDrawColor(30, 30, 35) surface.DrawOutlinedRect(0, 0, w, h, 1) draw.SimpleText("СИСТЕМА ОТРЯДОВ", "F4Menu_DonateHero", w/2, h/2, Color(1, 67, 29), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end if not isInSquad then -- Панель создания отряда local createPanel = vgui.Create("DPanel", self.contentPanel) createPanel:SetPos(ScalePos(12), ScalePos(85)) createPanel:SetSize(ScaleSize(1475), ScaleSize(200)) createPanel.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, Color(18, 18, 20)) surface.SetDrawColor(30, 30, 35) surface.DrawOutlinedRect(0, 0, w, h, 1) end local infoLabel = vgui.Create("DLabel", createPanel) infoLabel:SetPos(0, ScalePos(40)) infoLabel:SetSize(createPanel:GetWide(), ScaleSize(30)) infoLabel:SetFont("F4Menu_Category") infoLabel:SetTextColor(Color(200, 200, 200)) infoLabel:SetText("Вы не состоите в отряде") infoLabel:SetContentAlignment(5) local createBtn = vgui.Create("DButton", createPanel) createBtn:SetPos((createPanel:GetWide() - ScaleSize(300)) / 2, ScalePos(100)) createBtn:SetSize(ScaleSize(300), ScaleSize(50)) createBtn:SetText("") createBtn.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, s:IsHovered() and Color(1, 87, 39) or Color(1, 67, 29)) draw.SimpleText("Создать отряд", "F4Menu_Category", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end createBtn.DoClick = function() print("[SQUADS CLIENT] Нажата кнопка создания отряда") net.Start("ixSquadCreate") net.SendToServer() print("[SQUADS CLIENT] Сетевое сообщение отправлено") timer.Simple(0.5, function() if IsValid(self.f4Menu) then print("[SQUADS CLIENT] Обновление вкладки отрядов") self:SwitchTab("Отряды") end end) end -- Информация local infoText = vgui.Create("DLabel", self.contentPanel) infoText:SetPos(ScalePos(12), ScalePos(300)) infoText:SetSize(ScaleSize(1475), ScaleSize(300)) infoText:SetFont("F4Menu_Item") infoText:SetTextColor(Color(150, 150, 150)) infoText:SetText("• Максимум 16 человек в отряде\n• Приглашать можно только союзников из вашей фракции\n• Члены отряда видят позиции друг друга на компасе\n• Лидер может управлять составом отряда") infoText:SetWrap(true) infoText:SetContentAlignment(7) else -- Информация об отряде local infoPanel = vgui.Create("DPanel", self.contentPanel) infoPanel:SetPos(ScalePos(12), ScalePos(85)) infoPanel:SetSize(ScaleSize(1475), ScaleSize(100)) infoPanel.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, Color(18, 18, 20)) surface.SetDrawColor(1, 67, 29) surface.DrawRect(0, 0, w, 3) local memberCount = squad and #squad.members or 0 local maxMembers = ix.config.Get("squadMaxMembers", 16) local leaderName = squad and squad.memberData[squad.leader] and squad.memberData[squad.leader].name or "Неизвестно" draw.SimpleText("Лидер: " .. leaderName, "F4Menu_Category", ScalePos(30), h/2 - ScalePos(15), Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) draw.SimpleText("Состав: " .. memberCount .. "/" .. maxMembers, "F4Menu_Category", ScalePos(30), h/2 + ScalePos(15), Color(200, 200, 200), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) if isLeader then draw.SimpleText("Вы лидер отряда", "F4Menu_Item", w - ScalePos(30), h/2, Color(0, 255, 100), TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) end end -- Список членов отряда local membersScroll = vgui.Create("DScrollPanel", self.contentPanel) membersScroll:SetPos(ScalePos(12), ScalePos(200)) membersScroll:SetSize(ScaleSize(1475), ScaleSize(400)) membersScroll.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, Color(18, 18, 20)) end local vbar = membersScroll:GetVBar() vbar:SetWide(ScaleSize(8)) function vbar:Paint(w, h) draw.RoundedBox(4, 0, 0, w, h, Color(15, 15, 17)) end function vbar.btnUp:Paint(w, h) end function vbar.btnDown:Paint(w, h) end function vbar.btnGrip:Paint(w, h) draw.RoundedBox(4, 0, 0, w, h, Color(1, 67, 29)) end if squad and squad.members then for i, memberSteamID in ipairs(squad.members) do local memberData = squad.memberData[memberSteamID] if memberData then local memberPanel = vgui.Create("DPanel", membersScroll) memberPanel:Dock(TOP) memberPanel:DockMargin(ScalePos(10), ScalePos(5), ScalePos(10), ScalePos(5)) memberPanel:SetTall(ScaleSize(60)) memberPanel.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, Color(25, 25, 28)) surface.SetDrawColor(40, 40, 45) surface.DrawOutlinedRect(0, 0, w, h, 1) local statusColor = Color(100, 255, 100) surface.SetDrawColor(statusColor) surface.DrawRect(0, 0, 4, h) local nameText = memberData.name if memberData.isLeader then nameText = nameText .. " [ЛИДЕР]" end draw.SimpleText(nameText, "F4Menu_Category", ScalePos(20), h/2, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) local joinDate = os.date("%d.%m.%Y %H:%M", memberData.joinTime) draw.SimpleText("Присоединился: " .. joinDate, "F4Menu_InfoSmall", ScalePos(20), h/2 + ScalePos(20), Color(150, 150, 150), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) end -- Кнопка исключения (только для лидера и не для себя) if isLeader and memberSteamID ~= LocalPlayer():SteamID() then local kickBtn = vgui.Create("DButton", memberPanel) kickBtn:SetPos(memberPanel:GetWide() + ScaleSize(250), ScalePos(15)) kickBtn:SetSize(ScaleSize(130), ScaleSize(30)) kickBtn:SetText("") kickBtn.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, s:IsHovered() and Color(200, 50, 50) or Color(150, 40, 40)) draw.SimpleText("Исключить", "F4Menu_Item", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end kickBtn.DoClick = function() net.Start("ixSquadKick") net.WriteString(memberSteamID) net.SendToServer() timer.Simple(0.5, function() if IsValid(self.f4Menu) then self:SwitchTab("Отряды") end end) end end end end end -- Панель управления local controlPanel = vgui.Create("DPanel", self.contentPanel) controlPanel:SetPos(ScalePos(12), ScalePos(615)) controlPanel:SetSize(ScaleSize(1475), ScaleSize(80)) controlPanel.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, Color(18, 18, 20)) end if isLeader then -- Кнопка приглашения local inviteBtn = vgui.Create("DButton", controlPanel) inviteBtn:SetPos(ScalePos(20), ScalePos(15)) inviteBtn:SetSize(ScaleSize(350), ScaleSize(50)) inviteBtn:SetText("") inviteBtn.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, s:IsHovered() and Color(1, 87, 39) or Color(1, 67, 29)) draw.SimpleText("Пригласить игрока", "F4Menu_Category", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end inviteBtn.DoClick = function() self:OpenInviteMenu() end -- Кнопка расформирования local disbandBtn = vgui.Create("DButton", controlPanel) disbandBtn:SetPos(ScalePos(390), ScalePos(15)) disbandBtn:SetSize(ScaleSize(350), ScaleSize(50)) disbandBtn:SetText("") disbandBtn.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, s:IsHovered() and Color(200, 50, 50) or Color(150, 40, 40)) draw.SimpleText("Расформировать отряд", "F4Menu_Category", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end disbandBtn.DoClick = function() Derma_Query( "Вы уверены, что хотите расформировать отряд?", "Подтверждение", "Да", function() net.Start("ixSquadDisband") net.SendToServer() timer.Simple(0.5, function() if IsValid(self.f4Menu) then self:SwitchTab("Отряды") end end) end, "Нет" ) end else -- Кнопка выхода local leaveBtn = vgui.Create("DButton", controlPanel) leaveBtn:SetPos((controlPanel:GetWide() - ScaleSize(350)) / 2, ScalePos(15)) leaveBtn:SetSize(ScaleSize(350), ScaleSize(50)) leaveBtn:SetText("") leaveBtn.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, s:IsHovered() and Color(200, 50, 50) or Color(150, 40, 40)) draw.SimpleText("Покинуть отряд", "F4Menu_Category", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end leaveBtn.DoClick = function() Derma_Query( "Вы уверены, что хотите покинуть отряд?", "Подтверждение", "Да", function() net.Start("ixSquadLeave") net.SendToServer() timer.Simple(0.5, function() if IsValid(self.f4Menu) then self:SwitchTab("Отряды") end end) end, "Нет" ) end end end end -- Меню приглашения игроков function PLUGIN:OpenInviteMenu() local frame = vgui.Create("DFrame") frame:SetSize(ScaleSize(600), ScaleSize(500)) frame:Center() frame:SetTitle("") frame:SetDraggable(true) frame:ShowCloseButton(false) frame:MakePopup() frame.Paint = function(s, w, h) draw.RoundedBox(8, 0, 0, w, h, Color(18, 18, 20)) surface.SetDrawColor(1, 67, 29) surface.DrawRect(0, 0, w, 3) draw.SimpleText("Выберите игрока", "F4Menu_Category", w/2, ScalePos(25), Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end local closeBtn = vgui.Create("DButton", frame) closeBtn:SetPos(frame:GetWide() - ScaleSize(35), ScalePos(5)) closeBtn:SetSize(ScaleSize(30), ScaleSize(30)) closeBtn:SetText("✕") closeBtn:SetFont("F4Menu_Category") closeBtn:SetTextColor(Color(255, 255, 255)) closeBtn.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, s:IsHovered() and Color(200, 50, 50) or Color(0, 0, 0, 0)) end closeBtn.DoClick = function() frame:Close() end local playerList = vgui.Create("DScrollPanel", frame) playerList:SetPos(ScalePos(10), ScalePos(50)) playerList:SetSize(frame:GetWide() - ScaleSize(20), frame:GetTall() - ScaleSize(60)) local vbar = playerList:GetVBar() vbar:SetWide(ScaleSize(8)) function vbar:Paint(w, h) draw.RoundedBox(4, 0, 0, w, h, Color(15, 15, 17)) end function vbar.btnUp:Paint(w, h) end function vbar.btnDown:Paint(w, h) end function vbar.btnGrip:Paint(w, h) draw.RoundedBox(4, 0, 0, w, h, Color(1, 67, 29)) end local myChar = LocalPlayer():GetCharacter() if not myChar then return end local myFaction = myChar:GetFaction() for _, ply in ipairs(player.GetAll()) do if ply ~= LocalPlayer() and IsValid(ply) then local char = ply:GetCharacter() if char and char:GetFaction() == myFaction then local plyPanel = vgui.Create("DButton", playerList) plyPanel:Dock(TOP) plyPanel:DockMargin(ScalePos(5), ScalePos(5), ScalePos(5), 0) plyPanel:SetTall(ScaleSize(50)) plyPanel:SetText("") plyPanel.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, s:IsHovered() and Color(35, 35, 38) or Color(25, 25, 28)) draw.SimpleText(ply:Name(), "F4Menu_Category", ScalePos(15), h/2, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) draw.SimpleText("Пригласить →", "F4Menu_Item", w - ScalePos(15), h/2, Color(1, 67, 29), TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) end plyPanel.DoClick = function() print("[F4MENU] Отправка приглашения игроку: " .. ply:Name() .. " (SteamID: " .. ply:SteamID() .. ")") net.Start("ixSquadInvite") net.WriteString(ply:SteamID()) net.SendToServer() frame:Close() end end end end end -- Открытие меню по F4 с задержкой function PLUGIN:PlayerButtonDown(ply, button) if button == KEY_F4 and ply == LocalPlayer() then local currentTime = CurTime() if currentTime - self.lastF4Press >= self.f4Cooldown then self.lastF4Press = currentTime if IsValid(self.f4Menu) then self.f4Menu:Remove() else self:CreateF4Menu() end end end end -- Закрытие меню при смерти function PLUGIN:OnCharacterDeleted(character) if IsValid(self.f4Menu) then self.f4Menu:Remove() end end -- Обновление баланса при изменении function PLUGIN:CharacterMoneyChanged(character, money) if IsValid(self.f4Menu) and character == LocalPlayer():GetCharacter() then self:SwitchTab("Информация") end end