Files
bladwin-RP/lua/bladw_announcement/client/announcement_menu/cl_menu.lua
T
nocode 904cf3566c feat(announcement): add client-side announcement menu system
Implement a responsive announcement display system with support for multiple styles (loader with progress bar and border timer). Features include:
- Dynamic font scaling based on screen resolution
- Smooth intro/outro animations with easing
- Text wrapping for multi-line messages
- Customizable styling and duration
- Border timer visualization for remaining time
- Network message handling for server-sent announcements
2026-07-20 13:48:15 +02:00

237 lines
8.5 KiB
Lua

local rndx = include("lib/rndx.lua")
local logo = Material("nocode/announcement/logo.png")
-- client/ charge avant shared/, donc je m'assure que la table est là
BladW = BladW or {}
BladW.Announcement = BladW.Announcement or {}
local COL_ACCENT = Color(76, 119, 200)
local COL_WHITE = Color(255, 255, 255)
local COL_BG = Color(0, 0, 0, 200)
local COL_TRACK = Color(54, 54, 54, 200)
-- fonts responsives (refaites quand la résolution change)
local function AnnScale()
return math.min(ScrW() / 1920, ScrH() / 1080)
end
-- durées + easing de l'anim d'apparition/disparition
local INTRO_TIME = 0.45
local OUTRO_TIME = 0.35
local function EaseOutCubic(t) return 1 - (1 - t) ^ 3 end
local function BuildFonts()
local s = AnnScale()
surface.CreateFont("bladw_ann_title", {
font = "Poppins-SemiBold", size = math.max(1, math.Round(25 * s)), weight = 600, antialias = true,
})
surface.CreateFont("bladw_ann_text", {
font = "Poppins-SemiBold", size = math.max(1, math.Round(20 * s)), weight = 800, antialias = true,
})
end
BuildFonts()
hook.Add("OnScreenSizeChanged", "BladeWin:AnnouncementFonts", BuildFonts)
-- coupe le texte en lignes qui rentrent dans maxW (px) pour la police donnée
local function WrapText(text, font, maxW)
surface.SetFont(font)
local lines, line = {}, ""
for _, word in ipairs(string.Explode(" ", text or "")) do
local test = (line == "") and word or (line .. " " .. word)
if surface.GetTextSize(test) > maxW and line ~= "" then
lines[#lines + 1] = line
line = word
else
line = test
end
end
if line ~= "" then lines[#lines + 1] = line end
return lines
end
-- le contour du cadre sert de jauge : reste en colRemain, ce qui est passé en colDone (1 = plein, 0 = fini)
local function DrawBorderTimer(x, y, w, h, r, remaining, colRemain, colDone, thickness)
thickness = math.max(1, thickness or 3)
local pts = {}
local function line(x1, y1, x2, y2)
local steps = math.max(1, math.floor(math.Distance(x1, y1, x2, y2) / 4))
for i = 0, steps do
pts[#pts + 1] = { Lerp(i / steps, x1, x2), Lerp(i / steps, y1, y2) }
end
end
local function arc(cx, cy, a0, a1)
for i = 0, 6 do
local a = math.rad(Lerp(i / 6, a0, a1))
pts[#pts + 1] = { cx + math.cos(a) * r, cy + math.sin(a) * r }
end
end
-- sens horaire à partir du bord haut
line(x + r, y, x + w - r, y)
arc (x + w - r, y + r, 270, 360)
line(x + w, y + r, x + w, y + h - r)
arc (x + w - r, y + h - r, 0, 90)
line(x + w - r, y + h, x + r, y + h)
arc (x + r, y + h - r, 90, 180)
line(x, y + h - r, x, y + r)
arc (x + r, y + r, 180, 270)
local total = 0
for i = 1, #pts - 1 do
total = total + math.Distance(pts[i][1], pts[i][2], pts[i + 1][1], pts[i + 1][2])
end
if total == 0 then return end
local consumedLen = (1 - remaining) * total
draw.NoTexture()
local acc = 0
for i = 1, #pts - 1 do
local x1, y1 = pts[i][1], pts[i][2]
local x2, y2 = pts[i + 1][1], pts[i + 1][2]
local segLen = math.Distance(x1, y1, x2, y2)
surface.SetDrawColor(acc < consumedLen and colDone or colRemain)
surface.DrawTexturedRectRotated((x1 + x2) * 0.5, (y1 + y2) * 0.5,
segLen + thickness, thickness, -math.deg(math.atan2(y2 - y1, x2 - x1)))
acc = acc + segLen
end
end
-- état de l'annonce en cours (survit au reload)
BladW.Announcement.Active = BladW.Announcement.Active or false
BladW.Announcement.Title = BladW.Announcement.Title or "Annonce"
BladW.Announcement.Text = BladW.Announcement.Text or ""
BladW.Announcement.Style = BladW.Announcement.Style or "loader"
BladW.Announcement.Duration = BladW.Announcement.Duration or 10
BladW.Announcement.StartTime = BladW.Announcement.StartTime or 0
-- lance une annonce. style/duration vides = valeurs de la config
function BladW.Announcement.Show(title, text, style, duration)
local Conf = BladW.Announcement.Conf or {}
BladW.Announcement.Title = (title and title ~= "") and title or "Annonce"
BladW.Announcement.Text = text or ""
BladW.Announcement.Style = (style and style ~= "") and style or (Conf.announcement_style or "loader")
BladW.Announcement.Duration = (duration and duration > 0) and duration or (Conf.announcement_duration or 10)
BladW.Announcement.StartTime = CurTime()
BladW.Announcement.Active = true
if Conf.announcement_sound and Conf.announcement_sound ~= "" then
surface.PlaySound(Conf.announcement_sound)
end
end
-- le serveur m'envoie une annonce à afficher
net.Receive("bladWShowAnnouncement", function()
local title = net.ReadString()
local text = net.ReadString()
local style = net.ReadString()
local duration = net.ReadUInt(16)
BladW.Announcement.Show(title, text, style, duration)
end)
-- affichage (responsive + s'adapte au message)
hook.Add("HUDPaint", "BladeWin:Announcement", function()
if not BladW.Announcement.Active then return end
local ply = LocalPlayer()
if not IsValid(ply) then return end
local duration = BladW.Announcement.Duration or 10
local elapsed = CurTime() - BladW.Announcement.StartTime
-- temps écoulé -> on cache
if elapsed >= duration then
BladW.Announcement.Active = false
return
end
local style = BladW.Announcement.Style or "loader"
local title = BladW.Announcement.Title
local text = BladW.Announcement.Text
local remaining = math.Clamp(1 - (elapsed / duration), 0, 1)
-- échelle responsive
local s = AnnScale()
local padX = 26 * s
local padY = 18 * s
local gap = 8 * s
local logoS = 62 * s
local barW = 200 * s
local barH = 18 * s
local radius = 14 * s
-- wrap du message (largeur interne responsive, la vraie limite c'est le nb de caractères)
local wrapW = ScrW() * 0.8
local lines = WrapText(text, "bladw_ann_text", wrapW)
surface.SetFont("bladw_ann_title")
local titleW, titleH = surface.GetTextSize(title)
surface.SetFont("bladw_ann_text")
local _, lineH = surface.GetTextSize("Ay")
local msgW = 0
for _, l in ipairs(lines) do
msgW = math.max(msgW, (surface.GetTextSize(l)))
end
-- taille de la box selon le contenu
local contentW = math.max(titleW, msgW, style == "loader" and barW or 0)
local boxW = math.Clamp(contentW + padX * 2, 260 * s, ScrW() * 0.92)
local msgH = #lines * lineH
local boxH = padY + logoS + gap + titleH + gap + msgH
+ (style == "loader" and (gap + barH) or 0) + padY
-- anim : fondu + glisse du haut (et l'inverse quand ça part)
local appear = math.Clamp(elapsed / INTRO_TIME, 0, 1)
local disappear = math.Clamp((duration - elapsed) / OUTRO_TIME, 0, 1)
local anim = math.min(EaseOutCubic(appear), EaseOutCubic(disappear)) -- 0 -> 1 -> 0
local slide = (1 - anim) * 26 * s
local boxX = (ScrW() - boxW) * 0.5
local boxY = ScrH() * 0.035 - slide
local cx = boxX + boxW * 0.5
surface.SetAlphaMultiplier(anim)
-- fond
rndx.Draw(radius, boxX, boxY, boxW, boxH, nil, rndx.SHAPE_FIGMA + rndx.BLUR)
rndx.Draw(radius, boxX, boxY, boxW, boxH, COL_BG, rndx.SHAPE_FIGMA)
-- le loader garde son contour blanc (le simple lui a sa jauge sur le bord)
if style == "loader" then
rndx.DrawOutlined(radius, boxX, boxY, boxW, boxH, COL_WHITE, 3 * s, rndx.SHAPE_FIGMA)
end
-- contenu empilé et centré
local y = boxY + padY
rndx.DrawMaterial(0, cx - logoS * 0.5, y, logoS, logoS, COL_WHITE, logo)
y = y + logoS + gap
draw.SimpleText(title, "bladw_ann_title", cx, y, COL_WHITE, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP)
y = y + titleH + gap
for _, l in ipairs(lines) do
draw.SimpleText(l, "bladw_ann_text", cx, y, COL_WHITE, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP)
y = y + lineH
end
-- jauge de temps
if style == "loader" then
y = y + gap
local bx = cx - barW * 0.5
rndx.Draw(barH * 0.5, bx, y, barW, barH, COL_TRACK, rndx.SHAPE_FIGMA)
rndx.Draw(barH * 0.5, bx, y, barW * remaining, barH, COL_ACCENT, rndx.SHAPE_FIGMA)
else
-- le bord bleu qui se vide en blanc
DrawBorderTimer(boxX, boxY, boxW, boxH, radius, remaining, COL_ACCENT, COL_WHITE, 3 * s)
end
surface.SetAlphaMultiplier(1)
end)