2710 lines
109 KiB
Lua
2710 lines
109 KiB
Lua
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
|