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)