Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
e0a5424909
|
|||
|
4e436a05c3
|
|||
|
63e35a534c
|
|||
|
fad2323ab8
|
|||
|
6c95e7ed6f
|
|||
|
9ab34fe802
|
|||
|
c0a3e57a89
|
|||
|
41cc85a9e5
|
|||
|
448baba2c1
|
|||
|
e794c49c84
|
|||
|
f9773bd8ba
|
|||
|
082ff3eedd
|
@@ -10,8 +10,10 @@ NoCode.config = {
|
|||||||
-- • sans wildcard -> nom exact d'UN dossier : "bladw_announcement"
|
-- • sans wildcard -> nom exact d'UN dossier : "bladw_announcement"
|
||||||
-- • avec wildcard -> glob, charge TOUS ceux qui matchent :
|
-- • avec wildcard -> glob, charge TOUS ceux qui matchent :
|
||||||
-- "bladw_*" (préfixe) / "*_menu" (suffixe) / "bladw_*_v2" (milieu)
|
-- "bladw_*" (préfixe) / "*_menu" (suffixe) / "bladw_*_v2" (milieu)
|
||||||
|
-- • groupes "(a+b)" -> alternatives séparées par + :
|
||||||
|
-- "bladw_(ann*+warn)" = bladw_ann* OU bladw_warn (les alt peuvent avoir des *)
|
||||||
-- • "none" (ou vide/nil) -> loader désactivé, rien n'est chargé
|
-- • "none" (ou vide/nil) -> loader désactivé, rien n'est chargé
|
||||||
root = "none",
|
root = "bladw_(ann*+warn)",
|
||||||
-- Dossiers de bibliothèques : seulement envoyés au client (AddCSLuaFile),
|
-- Dossiers de bibliothèques : seulement envoyés au client (AddCSLuaFile),
|
||||||
-- jamais auto-inclus. Ils sont chargés à la demande via include("lib/...").
|
-- jamais auto-inclus. Ils sont chargés à la demande via include("lib/...").
|
||||||
libs = { "lib" },
|
libs = { "lib" },
|
||||||
@@ -237,16 +239,52 @@ local function GlobToPattern(glob)
|
|||||||
return "^" .. EscapePattern(glob):gsub("%%%*", ".*") .. "$"
|
return "^" .. EscapePattern(glob):gsub("%%%*", ".*") .. "$"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Étend les groupes "(a+b)" en plusieurs motifs plats.
|
||||||
|
-- "bladw_(ann*+warn)" -> { "bladw_ann*", "bladw_warn" }
|
||||||
|
-- Gère plusieurs groupes (produit cartésien) via récursion sur le suffixe.
|
||||||
|
local function ExpandPattern(pattern)
|
||||||
|
local open = pattern:find("(", 1, true)
|
||||||
|
if not open then return { pattern } end
|
||||||
|
|
||||||
|
local close = pattern:find(")", open + 1, true)
|
||||||
|
if not close then return { pattern } end -- parenthèse non fermée -> littéral
|
||||||
|
|
||||||
|
local prefix = pattern:sub(1, open - 1)
|
||||||
|
local group = pattern:sub(open + 1, close - 1)
|
||||||
|
local suffix = pattern:sub(close + 1)
|
||||||
|
|
||||||
|
local out = {}
|
||||||
|
for _, alt in ipairs(string.Explode("+", group)) do
|
||||||
|
for _, exp in ipairs(ExpandPattern(prefix .. alt .. suffix)) do
|
||||||
|
out[#out + 1] = exp
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Construit un testeur (dossier -> bool) pour un motif plat (avec ou sans "*").
|
||||||
|
local function Matcher(p)
|
||||||
|
if p:find("*", 1, true) then
|
||||||
|
local lua_pat = GlobToPattern(p)
|
||||||
|
return function(d) return d:match(lua_pat) ~= nil end
|
||||||
|
end
|
||||||
|
return function(d) return d == p end
|
||||||
|
end
|
||||||
|
|
||||||
local function FindRootFolders(pattern)
|
local function FindRootFolders(pattern)
|
||||||
local roots = {}
|
local roots = {}
|
||||||
|
|
||||||
-- Construit la fonction de test selon la présence ou non d'un wildcard.
|
-- On étend les groupes puis on teste chaque motif : un dossier matche si l'un d'eux matche.
|
||||||
local matches
|
local matchers = {}
|
||||||
if pattern:find("*", 1, true) then
|
for _, p in ipairs(ExpandPattern(pattern)) do
|
||||||
local lua_pat = GlobToPattern(pattern)
|
matchers[#matchers + 1] = Matcher(p)
|
||||||
matches = function(d) return d:match(lua_pat) ~= nil end
|
end
|
||||||
else
|
|
||||||
matches = function(d) return d == pattern end
|
local function matches(d)
|
||||||
|
for _, m in ipairs(matchers) do
|
||||||
|
if m(d) then return true end
|
||||||
|
end
|
||||||
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
local _, dirs1 = file.Find("*", "LUA")
|
local _, dirs1 = file.Find("*", "LUA")
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ local COL_MUTED = Color(160, 160, 160)
|
|||||||
local COL_WARN = Color(255, 200, 50)
|
local COL_WARN = Color(255, 200, 50)
|
||||||
local COL_LINE = Color(255, 255, 255, 60)
|
local COL_LINE = Color(255, 255, 255, 60)
|
||||||
|
|
||||||
|
-- couleur des contours extérieurs selon la config ("blue" ou "white")
|
||||||
|
local function OuterCol()
|
||||||
|
return (BladW.Announcement.Conf or {}).announcement_theme == "white" and COL_TEXT or COL_ACCENT
|
||||||
|
end
|
||||||
|
|
||||||
-- nb de caractères d'un champ (UTF-8, un accent = 1)
|
-- nb de caractères d'un champ (UTF-8, un accent = 1)
|
||||||
local function EntryLen(entry)
|
local function EntryLen(entry)
|
||||||
local v = entry:GetValue()
|
local v = entry:GetValue()
|
||||||
@@ -89,7 +94,7 @@ local function OpenAAdminMenu()
|
|||||||
frame.Paint = function(self, w, h)
|
frame.Paint = function(self, w, h)
|
||||||
rndx.Draw(20, 0, 0, w, h, nil, rndx.SHAPE_FIGMA + rndx.BLUR)
|
rndx.Draw(20, 0, 0, w, h, nil, rndx.SHAPE_FIGMA + rndx.BLUR)
|
||||||
rndx.Draw(20, 0, 0, w, h, COL_BG, rndx.SHAPE_FIGMA)
|
rndx.Draw(20, 0, 0, w, h, COL_BG, rndx.SHAPE_FIGMA)
|
||||||
rndx.DrawOutlined(20, 0, 0, w, h, COL_TEXT, 3, rndx.SHAPE_FIGMA)
|
rndx.DrawOutlined(20, 0, 0, w, h, OuterCol(), 3, rndx.SHAPE_FIGMA)
|
||||||
|
|
||||||
rndx.DrawMaterial(0, w * 0.5 - 30, 20, 60, 60, COL_TEXT, logo)
|
rndx.DrawMaterial(0, w * 0.5 - 30, 20, 60, 60, COL_TEXT, logo)
|
||||||
draw.SimpleText("Envoyer une annonce", "bladw_text", w * 0.5, 94, COL_TEXT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
draw.SimpleText("Envoyer une annonce", "bladw_text", w * 0.5, 94, COL_TEXT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ local COL_WHITE = Color(255, 255, 255)
|
|||||||
local COL_BG = Color(0, 0, 0, 200)
|
local COL_BG = Color(0, 0, 0, 200)
|
||||||
local COL_TRACK = Color(54, 54, 54, 200)
|
local COL_TRACK = Color(54, 54, 54, 200)
|
||||||
|
|
||||||
|
-- couleur des contours extérieurs selon la config ("blue" ou "white")
|
||||||
|
local function OuterCol()
|
||||||
|
return (BladW.Announcement.Conf or {}).announcement_theme == "white" and COL_WHITE or COL_ACCENT
|
||||||
|
end
|
||||||
|
|
||||||
-- fonts responsives (refaites quand la résolution change)
|
-- fonts responsives (refaites quand la résolution change)
|
||||||
local function AnnScale()
|
local function AnnScale()
|
||||||
return math.min(ScrW() / 1920, ScrH() / 1080)
|
return math.min(ScrW() / 1920, ScrH() / 1080)
|
||||||
@@ -203,9 +208,9 @@ hook.Add("HUDPaint", "BladeWin:Announcement", function()
|
|||||||
rndx.Draw(radius, boxX, boxY, boxW, boxH, nil, rndx.SHAPE_FIGMA + rndx.BLUR)
|
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)
|
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)
|
-- le loader garde un contour fixe (couleur selon le thème config ; le simple a sa jauge sur le bord)
|
||||||
if style == "loader" then
|
if style == "loader" then
|
||||||
rndx.DrawOutlined(radius, boxX, boxY, boxW, boxH, COL_WHITE, 3 * s, rndx.SHAPE_FIGMA)
|
rndx.DrawOutlined(radius, boxX, boxY, boxW, boxH, OuterCol(), 3 * s, rndx.SHAPE_FIGMA)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- contenu empilé et centré
|
-- contenu empilé et centré
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ BladW.Announcement.Conf = BladW.Announcement.Conf or {}
|
|||||||
|
|
||||||
BladW.Announcement.Conf = {
|
BladW.Announcement.Conf = {
|
||||||
announcement_style = "simple", -- "simple" ou "loader"
|
announcement_style = "simple", -- "simple" ou "loader"
|
||||||
|
announcement_theme = "white", -- couleur des contours extérieurs : "blue" ou "white"
|
||||||
announcement_duration = 10, -- durée d'affichage en secondes
|
announcement_duration = 10, -- durée d'affichage en secondes
|
||||||
announcement_max_length = 80, -- max de caractères pour le message
|
announcement_max_length = 80, -- max de caractères pour le message
|
||||||
announcement_sound = "announce.mp3", -- son à l'apparition ("" = rien), fichier dans sound/ | ex : garrysmod/content_downloaded.wav
|
announcement_sound = "announce.mp3", -- son à l'apparition ("" = rien), fichier dans sound/ | ex : garrysmod/content_downloaded.wav
|
||||||
|
|||||||
@@ -0,0 +1,529 @@
|
|||||||
|
local rndx = include("lib/rndx.lua")
|
||||||
|
local logo = Material("nocode/announcement/logo.png")
|
||||||
|
|
||||||
|
BladW = BladW or {}
|
||||||
|
BladW.Warn = BladW.Warn or {}
|
||||||
|
BladW.Warn.List = BladW.Warn.List or {}
|
||||||
|
|
||||||
|
-- palette
|
||||||
|
local COL_BG = Color(24, 24, 28)
|
||||||
|
local COL_PANEL = Color(38, 38, 44)
|
||||||
|
local COL_INPUT = Color(30, 30, 35)
|
||||||
|
local COL_LINE = Color(255, 255, 255, 22)
|
||||||
|
local COL_TEXT = Color(236, 236, 240)
|
||||||
|
local COL_MUTED = Color(150, 150, 160)
|
||||||
|
local COL_BLUE = Color(74, 108, 224)
|
||||||
|
local COL_RED = Color(226, 76, 76)
|
||||||
|
local COL_GREEN = Color(45, 170, 96)
|
||||||
|
local COL_ORANGE = Color(232, 168, 32)
|
||||||
|
local COL_SEL = Color(50, 50, 60)
|
||||||
|
local COL_BAR = Color(92, 92, 104) -- petit bout à gauche des joueurs (non sélectionné)
|
||||||
|
local COL_BADGE = Color(22, 22, 26) -- fond du badge "warns actifs" (gris foncé)
|
||||||
|
|
||||||
|
local function Lighten(c, a)
|
||||||
|
a = a or 22
|
||||||
|
return Color(math.min(c.r + a, 255), math.min(c.g + a, 255), math.min(c.b + a, 255), c.a)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- FontAwesome (vrais glyphes)
|
||||||
|
-- Le .ttf doit être chargé côté client : dépose un fa-solid-900.ttf
|
||||||
|
-- dans resource/fonts/. Change FA_FONT si ta police a un autre nom
|
||||||
|
-- de famille, et les codepoints si ta version de FA diffère.
|
||||||
|
-- ============================================================
|
||||||
|
local FA_FONT = "Font Awesome 6 Free Solid" -- nom de famille Windows (name ID 1), pas le typo "Font Awesome 6 Free"
|
||||||
|
local FA = {
|
||||||
|
search = 0xf002, -- magnifying-glass
|
||||||
|
shield = 0xf3ed, -- shield-halved (shield-alt en FA5)
|
||||||
|
card = 0xf2c2, -- id-card
|
||||||
|
copy = 0xf0c5, -- copy
|
||||||
|
trash = 0xf1f8, -- trash
|
||||||
|
}
|
||||||
|
|
||||||
|
local function FAIcon(cp, font, x, y, col, ax, ay)
|
||||||
|
draw.SimpleText(utf8.char(cp), font or "bw_fa", x, y, col, ax or TEXT_ALIGN_CENTER, ay or TEXT_ALIGN_CENTER)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- fonts responsives
|
||||||
|
local FS = 1
|
||||||
|
local function BuildFonts()
|
||||||
|
FS = math.Clamp(ScrH() / 1080, 0.7, 1.5)
|
||||||
|
surface.CreateFont("bw_title", { font = "Poppins-Bold", size = math.Round(24 * FS), weight = 700, antialias = true })
|
||||||
|
surface.CreateFont("bw_big", { font = "Poppins-SemiBold", size = math.Round(19 * FS), weight = 600, antialias = true })
|
||||||
|
surface.CreateFont("bw_med", { font = "Poppins-Medium", size = math.Round(15 * FS), weight = 500, antialias = true })
|
||||||
|
surface.CreateFont("bw_bold", { font = "Poppins-Bold", size = math.Round(15 * FS), weight = 700, antialias = true })
|
||||||
|
surface.CreateFont("bw_small", { font = "Poppins-Regular", size = math.Round(13 * FS), weight = 400, antialias = true })
|
||||||
|
surface.CreateFont("bw_fa", { font = FA_FONT, size = math.Round(15 * FS), weight = 900, extended = true })
|
||||||
|
surface.CreateFont("bw_fa_lg", { font = FA_FONT, size = math.Round(19 * FS), weight = 900, extended = true })
|
||||||
|
end
|
||||||
|
BuildFonts()
|
||||||
|
hook.Add("OnScreenSizeChanged", "bw_warn_fonts", BuildFonts)
|
||||||
|
|
||||||
|
-- temps relatif ("2 mois", "3 j"...)
|
||||||
|
local function RelTime(t)
|
||||||
|
local d = os.time() - (t or 0)
|
||||||
|
if d < 60 then return "à l'instant"
|
||||||
|
elseif d < 3600 then return math.floor(d / 60) .. " min"
|
||||||
|
elseif d < 86400 then return math.floor(d / 3600) .. " h"
|
||||||
|
elseif d < 2592000 then return math.floor(d / 86400) .. " j"
|
||||||
|
elseif d < 31536000 then return math.floor(d / 2592000) .. " mois"
|
||||||
|
else return math.floor(d / 31536000) .. " an(s)" end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function Btn(parent, label, col, font)
|
||||||
|
local b = vgui.Create("DButton", parent)
|
||||||
|
b:SetText("")
|
||||||
|
b.Paint = function(self, w, h)
|
||||||
|
rndx.Draw(8, 0, 0, w, h, self:IsHovered() and Lighten(col) or col, rndx.SHAPE_FIGMA)
|
||||||
|
draw.SimpleText(label, font or "bw_med", w * 0.5, h * 0.5, COL_TEXT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||||
|
end
|
||||||
|
return b
|
||||||
|
end
|
||||||
|
|
||||||
|
local function StyleEntry(e, ph, multiline)
|
||||||
|
e:SetFont("bw_med")
|
||||||
|
e:SetTextColor(COL_TEXT)
|
||||||
|
e:SetPlaceholderText(ph or "")
|
||||||
|
e:SetPaintBackground(false)
|
||||||
|
if multiline then e:SetMultiline(true) end
|
||||||
|
e.Paint = function(self, w, h)
|
||||||
|
rndx.Draw(8, 0, 0, w, h, COL_INPUT, rndx.SHAPE_FIGMA)
|
||||||
|
self:DrawTextEntryText(COL_TEXT, COL_BLUE, COL_TEXT)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function StyleScroll(sp)
|
||||||
|
local bar = sp:GetVBar()
|
||||||
|
bar:SetWide(8)
|
||||||
|
bar.Paint = function() end
|
||||||
|
bar.btnUp.Paint = function() end
|
||||||
|
bar.btnDown.Paint = function() end
|
||||||
|
bar.btnGrip.Paint = function(self, w, h) rndx.Draw(4, 0, 0, w, h, COL_LINE, rndx.SHAPE_FIGMA) end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function GetEntry(sid)
|
||||||
|
for _, e in ipairs(BladW.Warn.List) do
|
||||||
|
if e.sid == sid then return e end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Modal « Appliquer un warn »
|
||||||
|
-- ============================================================
|
||||||
|
local function OpenWarnModal(sid)
|
||||||
|
local reasons = (BladW.Warn.Conf or {}).reasons or {}
|
||||||
|
local chosen, chosenLabel = nil, "Choisissez une raison..."
|
||||||
|
|
||||||
|
local m = vgui.Create("DFrame")
|
||||||
|
m:SetSize(math.min(ScrW() * 0.42, 580), math.min(ScrH() * 0.5, 400))
|
||||||
|
m:Center()
|
||||||
|
m:SetTitle("")
|
||||||
|
m:ShowCloseButton(false)
|
||||||
|
m:MakePopup()
|
||||||
|
m:DockPadding(30, 66, 30, 26)
|
||||||
|
m.Paint = function(self, w, h)
|
||||||
|
rndx.Draw(16, 0, 0, w, h, COL_PANEL, rndx.SHAPE_FIGMA)
|
||||||
|
rndx.DrawOutlined(16, 0, 0, w, h, COL_LINE, 2, rndx.SHAPE_FIGMA)
|
||||||
|
draw.SimpleText("Appliquer un warn", "bw_title", w * 0.5, 36, COL_TEXT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||||
|
end
|
||||||
|
|
||||||
|
local mclose = vgui.Create("DButton", m)
|
||||||
|
mclose:SetSize(32, 32)
|
||||||
|
mclose:SetText("")
|
||||||
|
mclose.Paint = function(self, w, h)
|
||||||
|
rndx.Draw(8, 0, 0, w, h, self:IsHovered() and COL_RED or COL_BG, rndx.SHAPE_FIGMA)
|
||||||
|
draw.SimpleText("X", "bw_med", w * 0.5, h * 0.5, COL_RED, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||||
|
end
|
||||||
|
mclose.DoClick = function() m:Remove() end
|
||||||
|
m.PerformLayout = function(self, w, h) mclose:SetPos(w - 42, 14) end
|
||||||
|
|
||||||
|
-- dropdown des raisons
|
||||||
|
local rdd = vgui.Create("DButton", m)
|
||||||
|
rdd:Dock(TOP)
|
||||||
|
rdd:SetTall(46)
|
||||||
|
rdd:SetText("")
|
||||||
|
rdd.Paint = function(self, w, h)
|
||||||
|
rndx.Draw(8, 0, 0, w, h, COL_INPUT, rndx.SHAPE_FIGMA)
|
||||||
|
rndx.DrawOutlined(8, 0, 0, w, h, COL_LINE, 2, rndx.SHAPE_FIGMA)
|
||||||
|
draw.SimpleText(chosenLabel, "bw_med", 14, h * 0.5, chosen and COL_TEXT or COL_MUTED, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||||
|
draw.NoTexture()
|
||||||
|
surface.SetDrawColor(COL_MUTED)
|
||||||
|
local ax = w - 26
|
||||||
|
surface.DrawPoly({ { x = ax, y = h * 0.5 - 2 }, { x = ax + 10, y = h * 0.5 - 2 }, { x = ax + 5, y = h * 0.5 + 4 } })
|
||||||
|
end
|
||||||
|
rdd.DoClick = function(self)
|
||||||
|
local menu = DermaMenu()
|
||||||
|
menu:SetMinimumWidth(self:GetWide())
|
||||||
|
menu.Paint = function(s, w, h)
|
||||||
|
rndx.Draw(8, 0, 0, w, h, Color(20, 20, 24, 252), rndx.SHAPE_FIGMA)
|
||||||
|
rndx.DrawOutlined(8, 0, 0, w, h, COL_LINE, 1, rndx.SHAPE_FIGMA)
|
||||||
|
end
|
||||||
|
for _, r in ipairs(reasons) do
|
||||||
|
local o = menu:AddOption(r, function() chosen, chosenLabel = r, r end)
|
||||||
|
o:SetTall(30)
|
||||||
|
o:SetText("")
|
||||||
|
o.Paint = function(s, w, h)
|
||||||
|
if s:IsHovered() then rndx.Draw(6, 3, 1, w - 6, h - 2, COL_BLUE, rndx.SHAPE_FIGMA) end
|
||||||
|
draw.SimpleText(r, "bw_med", 12, h * 0.5, COL_TEXT, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local mx, my = self:LocalToScreen(0, self:GetTall() + 2)
|
||||||
|
menu:Open(mx, my)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- commentaire du staff
|
||||||
|
local clabel = vgui.Create("DLabel", m)
|
||||||
|
clabel:Dock(TOP)
|
||||||
|
clabel:DockMargin(0, 18, 0, 6)
|
||||||
|
clabel:SetTall(20)
|
||||||
|
clabel:SetFont("bw_med")
|
||||||
|
clabel:SetTextColor(COL_TEXT)
|
||||||
|
clabel:SetText("Commentaires du staff :")
|
||||||
|
|
||||||
|
local comment = vgui.Create("DTextEntry", m)
|
||||||
|
comment:Dock(TOP)
|
||||||
|
comment:SetTall(90)
|
||||||
|
StyleEntry(comment, "Joueur a surveiller...", true)
|
||||||
|
|
||||||
|
-- bouton Avertir
|
||||||
|
local warnBtn = Btn(m, "Avertir", COL_GREEN, "bw_big")
|
||||||
|
warnBtn:Dock(BOTTOM)
|
||||||
|
warnBtn:SetTall(46)
|
||||||
|
warnBtn:DockMargin(90, 16, 90, 0)
|
||||||
|
warnBtn.DoClick = function()
|
||||||
|
if not chosen then
|
||||||
|
surface.PlaySound("buttons/button10.wav")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
net.Start("bladw_warn_add")
|
||||||
|
net.WriteString(sid)
|
||||||
|
net.WriteString(chosen)
|
||||||
|
net.WriteString(comment:GetValue() or "")
|
||||||
|
net.SendToServer()
|
||||||
|
m:Remove()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Menu principal
|
||||||
|
-- ============================================================
|
||||||
|
function BladW.Warn.Open()
|
||||||
|
if IsValid(BladW.Warn.Frame) then BladW.Warn.Frame:Remove() end
|
||||||
|
|
||||||
|
local W = math.min(ScrW() * 0.72, 1180)
|
||||||
|
local H = math.min(ScrH() * 0.80, 750)
|
||||||
|
|
||||||
|
local frame = vgui.Create("DFrame")
|
||||||
|
BladW.Warn.Frame = frame
|
||||||
|
frame:SetSize(W, H)
|
||||||
|
frame:Center()
|
||||||
|
frame:SetTitle("")
|
||||||
|
frame:ShowCloseButton(false)
|
||||||
|
frame:MakePopup()
|
||||||
|
frame:DockPadding(16, 16, 16, 16)
|
||||||
|
frame.Paint = function(self, w, h)
|
||||||
|
local lw = w * 0.3 + 24 -- fin de la zone gauche (alignée sur la colonne joueurs)
|
||||||
|
|
||||||
|
-- côté droit : blur + voile (assez sombre mais on voit le fond)
|
||||||
|
rndx.Draw(18, 0, 0, w, h, nil, rndx.SHAPE_FIGMA + rndx.BLUR)
|
||||||
|
rndx.Draw(18, 0, 0, w, h, Color(24, 24, 28, 200), rndx.SHAPE_FIGMA)
|
||||||
|
|
||||||
|
-- côté gauche : couleur pleine (coins arrondis à gauche, carré à droite)
|
||||||
|
rndx.Draw(18, 0, 0, lw, h, COL_BG, rndx.SHAPE_FIGMA + rndx.NO_TR + rndx.NO_BR)
|
||||||
|
|
||||||
|
-- bordure blanche (comme les annonces)
|
||||||
|
rndx.DrawOutlined(18, 0, 0, w, h, Color(255, 255, 255), 3, rndx.SHAPE_FIGMA)
|
||||||
|
end
|
||||||
|
|
||||||
|
local selectedSID
|
||||||
|
|
||||||
|
-- ===== colonne gauche =====
|
||||||
|
local left = vgui.Create("DPanel", frame)
|
||||||
|
left:Dock(LEFT)
|
||||||
|
left:SetWide(W * 0.3)
|
||||||
|
left:DockMargin(0, 0, 16, 0)
|
||||||
|
left:SetPaintBackground(false)
|
||||||
|
|
||||||
|
local lhead = vgui.Create("DPanel", left)
|
||||||
|
lhead:Dock(TOP)
|
||||||
|
lhead:SetTall(46)
|
||||||
|
lhead:SetPaintBackground(false)
|
||||||
|
lhead.Paint = function(self, w, h)
|
||||||
|
rndx.DrawMaterial(0, 0, (h - 42) * 0.5, 42, 42, color_white, logo)
|
||||||
|
draw.SimpleText("Joueurs", "bw_title", 54, h * 0.5, COL_TEXT, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||||
|
end
|
||||||
|
|
||||||
|
local searchBox = vgui.Create("DPanel", left)
|
||||||
|
searchBox:Dock(TOP)
|
||||||
|
searchBox:DockMargin(0, 8, 0, 10)
|
||||||
|
searchBox:SetTall(40)
|
||||||
|
searchBox:SetPaintBackground(false)
|
||||||
|
searchBox.Paint = function(self, w, h)
|
||||||
|
rndx.Draw(8, 0, 0, w, h, COL_INPUT, rndx.SHAPE_FIGMA)
|
||||||
|
FAIcon(FA.search, "bw_fa", 22, h * 0.5, COL_MUTED)
|
||||||
|
end
|
||||||
|
|
||||||
|
local search = vgui.Create("DTextEntry", searchBox)
|
||||||
|
search:Dock(FILL)
|
||||||
|
search:DockMargin(38, 0, 10, 0)
|
||||||
|
search:SetFont("bw_med")
|
||||||
|
search:SetTextColor(COL_TEXT)
|
||||||
|
search:SetPlaceholderText("Rechercher un joueur")
|
||||||
|
search:SetPaintBackground(false)
|
||||||
|
search.Paint = function(self, w, h)
|
||||||
|
self:DrawTextEntryText(COL_TEXT, COL_BLUE, COL_TEXT)
|
||||||
|
end
|
||||||
|
|
||||||
|
local plist = vgui.Create("DScrollPanel", left)
|
||||||
|
plist:Dock(FILL)
|
||||||
|
StyleScroll(plist)
|
||||||
|
|
||||||
|
-- ===== colonne droite =====
|
||||||
|
local right = vgui.Create("DPanel", frame)
|
||||||
|
right:Dock(FILL)
|
||||||
|
right:SetPaintBackground(false)
|
||||||
|
|
||||||
|
local rhead = vgui.Create("DPanel", right)
|
||||||
|
rhead:Dock(TOP)
|
||||||
|
rhead:SetTall(58)
|
||||||
|
rhead:SetPaintBackground(false)
|
||||||
|
rhead.Paint = function(self, w, h)
|
||||||
|
draw.SimpleText("Historique", "bw_title", w * 0.5, 16, COL_TEXT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||||
|
FAIcon(FA.card, "bw_fa_lg", w * 0.5, 44, COL_TEXT)
|
||||||
|
end
|
||||||
|
|
||||||
|
local close = vgui.Create("DButton", rhead)
|
||||||
|
close:Dock(RIGHT)
|
||||||
|
close:SetWide(40)
|
||||||
|
close:SetText("")
|
||||||
|
close.Paint = function(self, w, h)
|
||||||
|
local y = (h - 34) * 0.5
|
||||||
|
rndx.Draw(8, w - 34, y, 34, 34, self:IsHovered() and COL_RED or COL_PANEL, rndx.SHAPE_FIGMA)
|
||||||
|
draw.SimpleText("X", "bw_big", w - 17, h * 0.5, COL_RED, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||||
|
end
|
||||||
|
close.DoClick = function() frame:Remove() end
|
||||||
|
|
||||||
|
local content = vgui.Create("DPanel", right)
|
||||||
|
content:Dock(FILL)
|
||||||
|
content:DockMargin(0, 8, 0, 0)
|
||||||
|
content:SetPaintBackground(false)
|
||||||
|
|
||||||
|
-- ===== refresh =====
|
||||||
|
local RefreshRight
|
||||||
|
|
||||||
|
local function RefreshPlayers()
|
||||||
|
plist:Clear()
|
||||||
|
local filter = string.lower(search:GetValue() or "")
|
||||||
|
|
||||||
|
for _, e in ipairs(BladW.Warn.List) do
|
||||||
|
if filter == "" or string.find(string.lower(e.name), filter, 1, true) then
|
||||||
|
local entry = e
|
||||||
|
local row = plist:Add("DButton")
|
||||||
|
row:Dock(TOP)
|
||||||
|
row:DockMargin(0, 0, 8, 8)
|
||||||
|
row:SetTall(52)
|
||||||
|
row:SetText("")
|
||||||
|
row.Paint = function(self, w, h)
|
||||||
|
local sel = selectedSID == entry.sid
|
||||||
|
rndx.Draw(8, 0, 0, w, h, sel and COL_SEL or COL_PANEL, rndx.SHAPE_FIGMA)
|
||||||
|
-- petit bout à gauche : bleu si sélectionné, gris clair sinon (coins gauche arrondis)
|
||||||
|
rndx.Draw(8, 0, 0, 5, h, sel and COL_BLUE or COL_BAR, rndx.SHAPE_FIGMA + rndx.NO_TR + rndx.NO_BR)
|
||||||
|
draw.SimpleText(entry.name, "bw_bold", 60, h * 0.5, COL_TEXT, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||||
|
|
||||||
|
if entry.staff then
|
||||||
|
FAIcon(FA.shield, "bw_fa", w - 28, h * 0.5, COL_ORANGE)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
row.DoClick = function() selectedSID = entry.sid RefreshRight() end
|
||||||
|
|
||||||
|
local av = vgui.Create("AvatarImage", row)
|
||||||
|
av:SetSize(34, 34)
|
||||||
|
av:SetPos(14, 9)
|
||||||
|
av:SetSteamID(entry.sid, 64)
|
||||||
|
av:SetMouseInputEnabled(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function RefreshRight()
|
||||||
|
content:Clear()
|
||||||
|
|
||||||
|
local e = selectedSID and GetEntry(selectedSID)
|
||||||
|
if not e then
|
||||||
|
local ph = vgui.Create("DPanel", content)
|
||||||
|
ph:Dock(FILL)
|
||||||
|
ph:SetPaintBackground(false)
|
||||||
|
ph.Paint = function(self, w, h)
|
||||||
|
draw.SimpleText("Sélectionne un joueur à gauche", "bw_med", w * 0.5, h * 0.4, COL_MUTED, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- carte joueur
|
||||||
|
local card = vgui.Create("DPanel", content)
|
||||||
|
card:Dock(TOP)
|
||||||
|
card:SetTall(90)
|
||||||
|
card:SetPaintBackground(false)
|
||||||
|
card.Paint = function(self, w, h)
|
||||||
|
rndx.Draw(12, 0, 0, w, h, COL_PANEL, rndx.SHAPE_FIGMA)
|
||||||
|
draw.SimpleText(e.name, "bw_big", 96, 30, COL_TEXT, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||||
|
draw.SimpleText("Steam ID :", "bw_small", 96, 56, COL_MUTED, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||||
|
|
||||||
|
local n = #e.warns
|
||||||
|
local txt = n .. (n > 1 and " Warns actifs" or " Warn actif")
|
||||||
|
surface.SetFont("bw_bold")
|
||||||
|
local bw = surface.GetTextSize(txt) + 30
|
||||||
|
rndx.Draw(6, w * 0.5 - bw * 0.5, h * 0.5 - 16, bw, 32, COL_BADGE, rndx.SHAPE_FIGMA)
|
||||||
|
draw.SimpleText(txt, "bw_bold", w * 0.5, h * 0.5, COL_RED, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||||
|
end
|
||||||
|
local av = vgui.Create("AvatarImage", card)
|
||||||
|
av:SetSize(56, 56)
|
||||||
|
av:SetPos(22, 17)
|
||||||
|
av:SetSteamID(e.sid, 64)
|
||||||
|
av:SetMouseInputEnabled(false)
|
||||||
|
|
||||||
|
-- bouton copie du SteamID
|
||||||
|
local copyBtn = vgui.Create("DButton", card)
|
||||||
|
copyBtn:SetText("")
|
||||||
|
copyBtn:SetSize(16, 16)
|
||||||
|
copyBtn:SetTooltip("Copier le SteamID")
|
||||||
|
copyBtn.Paint = function(self, w, h)
|
||||||
|
FAIcon(FA.copy, "bw_fa", w * 0.5, h * 0.5, self:IsHovered() and COL_TEXT or COL_MUTED)
|
||||||
|
end
|
||||||
|
copyBtn.DoClick = function() SetClipboardText(e.sid) end
|
||||||
|
card.PerformLayout = function(self, w, h)
|
||||||
|
surface.SetFont("bw_small")
|
||||||
|
local tw = surface.GetTextSize("Steam ID :")
|
||||||
|
copyBtn:SetPos(96 + tw + 8, 49)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ligne « Ajouter un warn »
|
||||||
|
local addRow = vgui.Create("DPanel", content)
|
||||||
|
addRow:Dock(TOP)
|
||||||
|
addRow:SetTall(64)
|
||||||
|
addRow:DockMargin(0, 10, 0, 4)
|
||||||
|
addRow:SetPaintBackground(false)
|
||||||
|
addRow.Paint = function(self, w, h)
|
||||||
|
surface.SetDrawColor(COL_LINE)
|
||||||
|
surface.DrawRect(0, h * 0.5, w * 0.5 - 100, 1)
|
||||||
|
surface.DrawRect(w * 0.5 + 100, h * 0.5, w * 0.5 - 100, 1)
|
||||||
|
end
|
||||||
|
local addBtn = Btn(addRow, "Ajouter un warn", COL_BLUE, "bw_big")
|
||||||
|
addBtn:SetSize(190, 42)
|
||||||
|
addRow.PerformLayout = function(self, w, h) addBtn:SetPos(w * 0.5 - 95, h * 0.5 - 21) end
|
||||||
|
addBtn.DoClick = function() OpenWarnModal(e.sid) end
|
||||||
|
|
||||||
|
-- ligne « Vider le casier » (en bas)
|
||||||
|
local clearRow = vgui.Create("DPanel", content)
|
||||||
|
clearRow:Dock(BOTTOM)
|
||||||
|
clearRow:SetTall(54)
|
||||||
|
clearRow:SetPaintBackground(false)
|
||||||
|
local clearBtn = Btn(clearRow, "Vider le casier", COL_ORANGE, "bw_med")
|
||||||
|
clearBtn:SetSize(190, 40)
|
||||||
|
clearBtn.Paint = function(self, w, h)
|
||||||
|
rndx.Draw(8, 0, 0, w, h, self:IsHovered() and Lighten(COL_ORANGE) or COL_ORANGE, rndx.SHAPE_FIGMA)
|
||||||
|
FAIcon(FA.trash, "bw_fa", 32, h * 0.5, COL_TEXT)
|
||||||
|
draw.SimpleText("Vider le casier", "bw_med", w * 0.5 + 14, h * 0.5, COL_TEXT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||||
|
end
|
||||||
|
clearRow.PerformLayout = function(self, w, h) clearBtn:SetPos(w - 190, h - 46) end
|
||||||
|
clearBtn.DoClick = function()
|
||||||
|
if #e.warns == 0 then return end
|
||||||
|
net.Start("bladw_warn_clear")
|
||||||
|
net.WriteString(e.sid)
|
||||||
|
net.SendToServer()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- liste des warns
|
||||||
|
local warns = vgui.Create("DScrollPanel", content)
|
||||||
|
warns:Dock(FILL)
|
||||||
|
warns:DockMargin(0, 6, 0, 6)
|
||||||
|
StyleScroll(warns)
|
||||||
|
|
||||||
|
for i, w in ipairs(e.warns) do
|
||||||
|
local idx, warn = i, w
|
||||||
|
local wc = warns:Add("DPanel")
|
||||||
|
wc:Dock(TOP)
|
||||||
|
wc:DockMargin(0, 0, 8, 10)
|
||||||
|
wc:SetTall(116)
|
||||||
|
wc:SetPaintBackground(false)
|
||||||
|
wc.Paint = function(self, cw, ch)
|
||||||
|
rndx.Draw(10, 0, 0, cw, ch, COL_PANEL, rndx.SHAPE_FIGMA)
|
||||||
|
draw.SimpleText("Warn émis par :", "bw_med", 16, 16, COL_TEXT, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
|
||||||
|
draw.SimpleText(warn.issuer ~= "" and warn.issuer or "?", "bw_med", 168, 16, COL_MUTED, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
|
||||||
|
draw.SimpleText("Date :", "bw_med", 16, 40, COL_TEXT, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
|
||||||
|
if warn.time and warn.time > 0 then
|
||||||
|
local d = ("(%s) %s"):format(RelTime(warn.time), os.date("%d/%m/%Y, %I:%M %p", warn.time))
|
||||||
|
draw.SimpleText(d, "bw_med", 168, 40, COL_MUTED, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
|
||||||
|
end
|
||||||
|
draw.SimpleText("Raison :", "bw_med", 16, 64, COL_TEXT, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
|
||||||
|
rndx.Draw(6, 14, ch - 40, cw - 28, 30, COL_INPUT, rndx.SHAPE_FIGMA)
|
||||||
|
draw.SimpleText(warn.reason or "", "bw_small", 26, ch - 25, COL_TEXT, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||||
|
end
|
||||||
|
|
||||||
|
local del = Btn(wc, "Supprimer", COL_RED, "bw_med")
|
||||||
|
del:SetSize(150, 36)
|
||||||
|
wc.PerformLayout = function(self, cw, ch) del:SetPos(cw - 164, 14) end
|
||||||
|
del.DoClick = function()
|
||||||
|
net.Start("bladw_warn_remove")
|
||||||
|
net.WriteString(e.sid)
|
||||||
|
net.WriteUInt(idx, 8)
|
||||||
|
net.SendToServer()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
frame.RefreshAll = function()
|
||||||
|
RefreshPlayers()
|
||||||
|
RefreshRight()
|
||||||
|
end
|
||||||
|
|
||||||
|
search.OnChange = function() RefreshPlayers() end
|
||||||
|
|
||||||
|
RefreshPlayers()
|
||||||
|
RefreshRight()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Réseau
|
||||||
|
-- ============================================================
|
||||||
|
net.Receive("bladw_warn_open", function()
|
||||||
|
BladW.Warn.Open()
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- annonce d'un warn dans le chat de base
|
||||||
|
net.Receive("bladw_warn_chat", function()
|
||||||
|
local target = net.ReadString()
|
||||||
|
local admin = net.ReadString()
|
||||||
|
local reason = net.ReadString()
|
||||||
|
chat.AddText(
|
||||||
|
COL_ORANGE, "[Warn] ",
|
||||||
|
COL_TEXT, target,
|
||||||
|
COL_MUTED, " a été averti par ",
|
||||||
|
COL_TEXT, admin,
|
||||||
|
COL_MUTED, " — ",
|
||||||
|
COL_RED, reason
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
|
net.Receive("bladw_warn_sync", function()
|
||||||
|
local list = {}
|
||||||
|
local n = net.ReadUInt(8)
|
||||||
|
for i = 1, n do
|
||||||
|
local e = { name = net.ReadString(), sid = net.ReadString(), staff = net.ReadBool(), warns = {} }
|
||||||
|
local wn = net.ReadUInt(8)
|
||||||
|
for j = 1, wn do
|
||||||
|
e.warns[j] = {
|
||||||
|
issuer = net.ReadString(),
|
||||||
|
time = net.ReadUInt(32),
|
||||||
|
reason = net.ReadString(),
|
||||||
|
comment = net.ReadString(),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
list[i] = e
|
||||||
|
end
|
||||||
|
BladW.Warn.List = list
|
||||||
|
|
||||||
|
if IsValid(BladW.Warn.Frame) and BladW.Warn.Frame.RefreshAll then
|
||||||
|
BladW.Warn.Frame.RefreshAll()
|
||||||
|
end
|
||||||
|
end)
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
BladW = BladW or {}
|
||||||
|
BladW.Warn = BladW.Warn or {}
|
||||||
|
|
||||||
|
-- envoie au client la liste des joueurs connectés + leurs warns
|
||||||
|
local function SyncTo(ply)
|
||||||
|
if not IsValid(ply) then return end
|
||||||
|
|
||||||
|
local players = player.GetAll()
|
||||||
|
|
||||||
|
net.Start("bladw_warn_sync")
|
||||||
|
net.WriteUInt(#players, 8)
|
||||||
|
for _, p in ipairs(players) do
|
||||||
|
local sid = p:SteamID64() or ""
|
||||||
|
local warns = BladW.Warn.Get(sid)
|
||||||
|
local isStaff = p:IsAdmin() or table.HasValue((BladW.Warn.Conf or {}).staff_groups or {}, p:GetUserGroup())
|
||||||
|
|
||||||
|
net.WriteString(p:Nick())
|
||||||
|
net.WriteString(sid)
|
||||||
|
net.WriteBool(isStaff)
|
||||||
|
net.WriteUInt(math.min(#warns, 255), 8)
|
||||||
|
for i = 1, math.min(#warns, 255) do
|
||||||
|
local w = warns[i]
|
||||||
|
net.WriteString(w.issuer or "")
|
||||||
|
net.WriteUInt(w.time or 0, 32)
|
||||||
|
net.WriteString(w.reason or "")
|
||||||
|
net.WriteString(w.comment or "")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
net.Send(ply)
|
||||||
|
end
|
||||||
|
BladW.Warn.SyncTo = SyncTo
|
||||||
|
|
||||||
|
-- ouvre le menu pour un joueur autorisé
|
||||||
|
local function OpenFor(ply)
|
||||||
|
if not IsValid(ply) or not BladW.Warn.HasAccess(ply) then return end
|
||||||
|
net.Start("bladw_warn_open")
|
||||||
|
net.Send(ply)
|
||||||
|
SyncTo(ply)
|
||||||
|
end
|
||||||
|
|
||||||
|
concommand.Add("bladw_warns", function(ply) OpenFor(ply) end)
|
||||||
|
|
||||||
|
hook.Add("PlayerSay", "BladW:WarnChatCmd", function(ply, text)
|
||||||
|
local cmd = string.lower(string.Trim(text))
|
||||||
|
if cmd ~= "/warns" and cmd ~= "!warns" and cmd ~= "/warn" then return end
|
||||||
|
OpenFor(ply)
|
||||||
|
return "" -- on retire la commande du chat
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- un admin ajoute un warn
|
||||||
|
net.Receive("bladw_warn_add", function(len, ply)
|
||||||
|
if not IsValid(ply) or not BladW.Warn.HasAccess(ply) then return end
|
||||||
|
|
||||||
|
local sid = net.ReadString()
|
||||||
|
local reason = string.sub(net.ReadString(), 1, 100)
|
||||||
|
local comment = string.sub(net.ReadString(), 1, 300)
|
||||||
|
if sid == "" or reason == "" then return end
|
||||||
|
|
||||||
|
BladW.Warn.AddWarn(sid, {
|
||||||
|
issuer = ply:Nick(),
|
||||||
|
issuerid = ply:SteamID64(),
|
||||||
|
time = os.time(),
|
||||||
|
reason = reason,
|
||||||
|
comment = comment,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- annonce le warn dans le chat de base
|
||||||
|
local Conf = BladW.Warn.Conf or {}
|
||||||
|
if Conf.chat_announce ~= false then
|
||||||
|
local target = player.GetBySteamID64(sid)
|
||||||
|
local targetName = IsValid(target) and target:Nick() or sid
|
||||||
|
|
||||||
|
local recips
|
||||||
|
local scope = Conf.chat_scope or "all"
|
||||||
|
if scope == "target" then
|
||||||
|
recips = IsValid(target) and { target } or {}
|
||||||
|
elseif scope == "staff" then
|
||||||
|
recips = {}
|
||||||
|
for _, p in ipairs(player.GetAll()) do
|
||||||
|
if BladW.Warn.HasAccess(p) then recips[#recips + 1] = p end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- recips == nil -> tout le monde ; sinon on n'envoie que si la liste n'est pas vide
|
||||||
|
if not recips or #recips > 0 then
|
||||||
|
net.Start("bladw_warn_chat")
|
||||||
|
net.WriteString(targetName)
|
||||||
|
net.WriteString(ply:Nick())
|
||||||
|
net.WriteString(reason)
|
||||||
|
if recips then net.Send(recips) else net.Broadcast() end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
SyncTo(ply)
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- un admin supprime un warn précis
|
||||||
|
net.Receive("bladw_warn_remove", function(len, ply)
|
||||||
|
if not IsValid(ply) or not BladW.Warn.HasAccess(ply) then return end
|
||||||
|
|
||||||
|
local sid = net.ReadString()
|
||||||
|
local index = net.ReadUInt(8)
|
||||||
|
BladW.Warn.RemoveWarn(sid, index)
|
||||||
|
|
||||||
|
SyncTo(ply)
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- un admin vide le casier d'un joueur
|
||||||
|
net.Receive("bladw_warn_clear", function(len, ply)
|
||||||
|
if not IsValid(ply) or not BladW.Warn.HasAccess(ply) then return end
|
||||||
|
|
||||||
|
local sid = net.ReadString()
|
||||||
|
BladW.Warn.ClearWarns(sid)
|
||||||
|
|
||||||
|
SyncTo(ply)
|
||||||
|
end)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
util.AddNetworkString("bladw_warn_open") -- ouvre le menu chez le client
|
||||||
|
util.AddNetworkString("bladw_warn_sync") -- serveur -> client : joueurs + leurs warns
|
||||||
|
util.AddNetworkString("bladw_warn_add") -- client -> serveur : ajouter un warn
|
||||||
|
util.AddNetworkString("bladw_warn_remove") -- client -> serveur : supprimer un warn
|
||||||
|
util.AddNetworkString("bladw_warn_clear") -- client -> serveur : vider le casier
|
||||||
|
util.AddNetworkString("bladw_warn_chat") -- serveur -> clients : annonce du warn dans le chat
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
BladW = BladW or {}
|
||||||
|
BladW.Warn = BladW.Warn or {}
|
||||||
|
|
||||||
|
-- le droit géré par l'admin mod (SAM / ULX / FAdmin)
|
||||||
|
local PRIV = "bladw_warn"
|
||||||
|
BladW.Warn.Privilege = PRIV
|
||||||
|
|
||||||
|
-- enregistre le droit dans l'admin mod présent
|
||||||
|
local function Register()
|
||||||
|
if sam and sam.permissions and sam.permissions.add then
|
||||||
|
pcall(sam.permissions.add, PRIV, "BladW Warn", "admin")
|
||||||
|
end
|
||||||
|
if ULib and ULib.ucl and ULib.ucl.registerAccess then
|
||||||
|
pcall(ULib.ucl.registerAccess, PRIV, "admin", "Gérer les warns BladW", "BladW")
|
||||||
|
end
|
||||||
|
if FAdmin and FAdmin.Access and FAdmin.Access.AddPrivilege then
|
||||||
|
pcall(FAdmin.Access.AddPrivilege, PRIV, 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
hook.Add("SAM.LoadPermissions", "BladW:WarnPriv", Register)
|
||||||
|
hook.Add("InitPostEntity", "BladW:WarnPriv", Register)
|
||||||
|
Register()
|
||||||
|
|
||||||
|
-- ce joueur peut-il gérer les warns ?
|
||||||
|
function BladW.Warn.HasAccess(ply)
|
||||||
|
if not IsValid(ply) then return false end
|
||||||
|
|
||||||
|
if sam and sam.player and sam.player.has_permission then
|
||||||
|
return sam.player.has_permission(ply, PRIV)
|
||||||
|
end
|
||||||
|
if ULib and ULib.ucl and ULib.ucl.query then
|
||||||
|
return ULib.ucl.query(ply, PRIV)
|
||||||
|
end
|
||||||
|
if FAdmin and FAdmin.Access and FAdmin.Access.PlayerHasPrivilege then
|
||||||
|
return FAdmin.Access.PlayerHasPrivilege(ply, PRIV)
|
||||||
|
end
|
||||||
|
|
||||||
|
return ply:IsAdmin()
|
||||||
|
end
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
BladW = BladW or {}
|
||||||
|
BladW.Warn = BladW.Warn or {}
|
||||||
|
|
||||||
|
-- stockage des warns : { [steamid64] = { { issuer, issuerid, time, reason, comment }, ... } }
|
||||||
|
local DIR = "bladw_warn"
|
||||||
|
local FILE = "bladw_warn/warns.json"
|
||||||
|
|
||||||
|
BladW.Warn.Data = BladW.Warn.Data or {}
|
||||||
|
|
||||||
|
function BladW.Warn.Save()
|
||||||
|
file.CreateDir(DIR)
|
||||||
|
file.Write(FILE, util.TableToJSON(BladW.Warn.Data))
|
||||||
|
end
|
||||||
|
|
||||||
|
function BladW.Warn.Load()
|
||||||
|
local raw = file.Read(FILE, "DATA")
|
||||||
|
BladW.Warn.Data = (raw and util.JSONToTable(raw)) or {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function BladW.Warn.Get(sid)
|
||||||
|
return BladW.Warn.Data[sid] or {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function BladW.Warn.AddWarn(sid, entry)
|
||||||
|
if not sid or sid == "" then return end
|
||||||
|
BladW.Warn.Data[sid] = BladW.Warn.Data[sid] or {}
|
||||||
|
table.insert(BladW.Warn.Data[sid], entry)
|
||||||
|
BladW.Warn.Save()
|
||||||
|
end
|
||||||
|
|
||||||
|
function BladW.Warn.RemoveWarn(sid, index)
|
||||||
|
local t = BladW.Warn.Data[sid]
|
||||||
|
if t and t[index] then
|
||||||
|
table.remove(t, index)
|
||||||
|
if #t == 0 then BladW.Warn.Data[sid] = nil end
|
||||||
|
BladW.Warn.Save()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function BladW.Warn.ClearWarns(sid)
|
||||||
|
BladW.Warn.Data[sid] = nil
|
||||||
|
BladW.Warn.Save()
|
||||||
|
end
|
||||||
|
|
||||||
|
BladW.Warn.Load()
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
BladW = BladW or {}
|
||||||
|
BladW.Warn = BladW.Warn or {}
|
||||||
|
BladW.Warn.Conf = BladW.Warn.Conf or {}
|
||||||
|
|
||||||
|
-- config des warns
|
||||||
|
BladW.Warn.Conf = {
|
||||||
|
-- les raisons proposées dans le menu déroulant du modal
|
||||||
|
reasons = {
|
||||||
|
"RDM",
|
||||||
|
"VDM",
|
||||||
|
"No Fear RP",
|
||||||
|
"Fail RP",
|
||||||
|
"Troll",
|
||||||
|
"Metagaming",
|
||||||
|
"Powergaming",
|
||||||
|
"Propos déplacés / insultes",
|
||||||
|
"Non-respect du staff",
|
||||||
|
"Publicité",
|
||||||
|
"Bug abuse",
|
||||||
|
},
|
||||||
|
|
||||||
|
-- annonce le warn dans le chat de base
|
||||||
|
chat_announce = true,
|
||||||
|
-- à qui : "all" (tout le monde) | "target" (juste l'averti) | "staff" (les admins)
|
||||||
|
chat_scope = "target",
|
||||||
|
|
||||||
|
-- groupes considérés comme "staff" (affiche le bouclier). IsAdmin() est toujours staff.
|
||||||
|
staff_groups = { "superadmin", "admin", "moderator", "modo", "helper", "support" },
|
||||||
|
}
|
||||||
Binary file not shown.
Reference in New Issue
Block a user