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

2710 lines
109 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
local PLUGIN = PLUGIN
-- Материалы
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