41cc85a9e5
Implement pattern group expansion allowing alternatives separated by '+' within parentheses. Examples: - "bladw_(ann*+warn)" expands to ["bladw_ann*", "bladw_warn"] - Supports multiple groups with recursive Cartesian product expansion - Refactor matching logic into separate Matcher() function for clarity - Update root pattern in config from "bladw_announcement*" to "bladw_(ann*+warn)"
378 lines
12 KiB
Lua
378 lines
12 KiB
Lua
-- ============================================================
|
|
-- AUTOLOADER nocode — Dual-path récursif
|
|
-- Fichier : lua/autorun/bladWinRpMenu_loader.lua
|
|
-- ============================================================
|
|
|
|
local NoCode = {}
|
|
|
|
NoCode.config = {
|
|
-- Dossier(s)-module(s) à charger.
|
|
-- • sans wildcard -> nom exact d'UN dossier : "bladw_announcement"
|
|
-- • avec wildcard -> glob, charge TOUS ceux qui matchent :
|
|
-- "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é
|
|
root = "bladw_(ann*+warn)",
|
|
-- Dossiers de bibliothèques : seulement envoyés au client (AddCSLuaFile),
|
|
-- jamais auto-inclus. Ils sont chargés à la demande via include("lib/...").
|
|
libs = { "lib" },
|
|
safe_load = true,
|
|
verbose = true,
|
|
exclude = { "template.lua", "disabled", "_dev" },
|
|
}
|
|
|
|
-- ============================================================
|
|
-- Logs
|
|
-- ============================================================
|
|
|
|
local C = {
|
|
ok = Color(100, 220, 100),
|
|
err = Color(255, 80, 80),
|
|
info = Color(120, 180, 255),
|
|
warn = Color(255, 200, 50),
|
|
dim = Color(160, 160, 160),
|
|
title = Color(200, 140, 255),
|
|
}
|
|
|
|
local function Log(col, msg)
|
|
MsgC(col, msg .. "\n")
|
|
end
|
|
|
|
-- ============================================================
|
|
-- Utilitaires
|
|
-- ============================================================
|
|
|
|
local function StartsWith(str, prefix)
|
|
return str:sub(1, #prefix) == prefix
|
|
end
|
|
|
|
local function IsExcluded(name)
|
|
for _, ex in ipairs(NoCode.config.exclude) do
|
|
if name == ex then return true end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function SortFiles(files)
|
|
table.sort(files, function(a, b)
|
|
local function rank(n)
|
|
if StartsWith(n, "sh_") then return 1 end
|
|
if StartsWith(n, "sv_") then return 2 end
|
|
if StartsWith(n, "cl_") then return 3 end
|
|
return 4
|
|
end
|
|
return rank(a) < rank(b)
|
|
end)
|
|
end
|
|
|
|
--- Fusionne deux tables en évitant les doublons
|
|
local function Merge(t1, t2)
|
|
local seen = {}
|
|
local result = {}
|
|
for _, v in ipairs(t1 or {}) do
|
|
if not seen[v] then seen[v] = true; table.insert(result, v) end
|
|
end
|
|
for _, v in ipairs(t2 or {}) do
|
|
if not seen[v] then seen[v] = true; table.insert(result, v) end
|
|
end
|
|
return result
|
|
end
|
|
|
|
-- ============================================================
|
|
-- Compteurs
|
|
-- ============================================================
|
|
|
|
local stats = { loaded = 0, sent = 0, errors = 0 }
|
|
|
|
-- ============================================================
|
|
-- Chargement d'un fichier unique
|
|
-- ============================================================
|
|
|
|
local function LoadFile(path)
|
|
local filename = path:match("([^/]+)$")
|
|
local realm, shouldLoad, shouldSend
|
|
|
|
if StartsWith(filename, "sh_") then
|
|
realm = "SHARED"
|
|
shouldSend = SERVER
|
|
shouldLoad = true
|
|
|
|
elseif StartsWith(filename, "sv_") then
|
|
realm = "SERVER"
|
|
shouldLoad = SERVER
|
|
|
|
elseif StartsWith(filename, "cl_") then
|
|
realm = "CLIENT"
|
|
shouldSend = SERVER
|
|
shouldLoad = not SERVER
|
|
|
|
else
|
|
return
|
|
end
|
|
|
|
if shouldSend then
|
|
AddCSLuaFile(path)
|
|
stats.sent = stats.sent + 1
|
|
if NoCode.config.verbose and not shouldLoad then
|
|
Log(C.info, (" [→] [%s] %s"):format(realm, path))
|
|
end
|
|
end
|
|
|
|
if not shouldLoad then return end
|
|
|
|
if NoCode.config.safe_load then
|
|
local ok, err = pcall(include, path)
|
|
if ok then
|
|
stats.loaded = stats.loaded + 1
|
|
if NoCode.config.verbose then
|
|
Log(C.ok, (" [✓] [%s] %s"):format(realm, path))
|
|
end
|
|
else
|
|
stats.errors = stats.errors + 1
|
|
Log(C.err, (" [✗] [%s] %s"):format(realm, path))
|
|
Log(C.err, (" ↳ %s"):format(tostring(err)))
|
|
end
|
|
else
|
|
include(path)
|
|
stats.loaded = stats.loaded + 1
|
|
if NoCode.config.verbose then
|
|
Log(C.ok, (" [✓] [%s] %s"):format(realm, path))
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ============================================================
|
|
-- Scan récursif — cherche TOUJOURS dans LUA et GAME en même temps
|
|
-- ============================================================
|
|
|
|
local function LoadFolder(dir, depth)
|
|
depth = depth or 0
|
|
|
|
local basename = dir:match("([^/]+)$") or dir
|
|
if IsExcluded(basename) then
|
|
Log(C.warn, (" [~] Dossier ignoré : %s"):format(dir))
|
|
return
|
|
end
|
|
|
|
-- On cherche dans les deux paths et on fusionne les résultats
|
|
local files1, dirs1 = file.Find(dir .. "/*", "LUA")
|
|
local files2, dirs2 = file.Find("lua/" .. dir .. "/*", "GAME")
|
|
|
|
local files = Merge(files1, files2)
|
|
local dirs = Merge(dirs1, dirs2)
|
|
|
|
if #files == 0 and #dirs == 0 then
|
|
if NoCode.config.verbose and depth > 0 then
|
|
Log(C.warn, (" [!] Dossier vide ou introuvable : %s"):format(dir))
|
|
end
|
|
return
|
|
end
|
|
|
|
if NoCode.config.verbose and depth > 0 then
|
|
Log(C.dim, string.rep(" ", depth) .. "📂 " .. basename .. "/")
|
|
end
|
|
|
|
SortFiles(files)
|
|
|
|
for _, fname in ipairs(files) do
|
|
if fname:EndsWith(".lua") and not IsExcluded(fname) then
|
|
LoadFile(dir .. "/" .. fname)
|
|
end
|
|
end
|
|
|
|
-- Récursion sur tous les sous-dossiers sans limite de profondeur
|
|
for _, subdir in ipairs(dirs) do
|
|
if not IsExcluded(subdir) then
|
|
LoadFolder(dir .. "/" .. subdir, depth + 1)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ============================================================
|
|
-- Envoi récursif d'une bibliothèque au client (sans l'inclure)
|
|
-- Les fichiers de lib/ ne suivent pas le préfixe sh_/sv_/cl_ ;
|
|
-- on les expédie simplement pour que include("lib/...") fonctionne.
|
|
-- ============================================================
|
|
|
|
local function SendFolder(dir)
|
|
if not SERVER then return end
|
|
|
|
local files1, dirs1 = file.Find(dir .. "/*", "LUA")
|
|
local files2, dirs2 = file.Find("lua/" .. dir .. "/*", "GAME")
|
|
local files = Merge(files1, files2)
|
|
local dirs = Merge(dirs1, dirs2)
|
|
|
|
for _, fname in ipairs(files) do
|
|
if fname:EndsWith(".lua") and not IsExcluded(fname) then
|
|
local path = dir .. "/" .. fname
|
|
AddCSLuaFile(path)
|
|
stats.sent = stats.sent + 1
|
|
if NoCode.config.verbose then
|
|
Log(C.info, (" [→] [LIB] %s"):format(path))
|
|
end
|
|
end
|
|
end
|
|
|
|
for _, subdir in ipairs(dirs) do
|
|
if not IsExcluded(subdir) then
|
|
SendFolder(dir .. "/" .. subdir)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ============================================================
|
|
-- Découverte des dossiers-modules
|
|
-- (file.Find ne supporte le wildcard que sur le dernier segment,
|
|
-- donc on liste la racine lua/ puis on filtre nous-mêmes.)
|
|
-- • pattern sans "*" -> match exact (un seul dossier)
|
|
-- • pattern avec "*" -> glob converti en motif Lua (préfixe/suffixe/milieu)
|
|
-- ============================================================
|
|
|
|
--- Échappe les caractères magiques d'un motif Lua.
|
|
local function EscapePattern(s)
|
|
return (s:gsub("([%(%)%.%%%+%-%*%?%[%]%^%$])", "%%%1"))
|
|
end
|
|
|
|
--- Transforme un glob simple ("bladw_*") en motif Lua ancré ("^bladw_.*$").
|
|
local function GlobToPattern(glob)
|
|
return "^" .. EscapePattern(glob):gsub("%%%*", ".*") .. "$"
|
|
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 roots = {}
|
|
|
|
-- On étend les groupes puis on teste chaque motif : un dossier matche si l'un d'eux matche.
|
|
local matchers = {}
|
|
for _, p in ipairs(ExpandPattern(pattern)) do
|
|
matchers[#matchers + 1] = Matcher(p)
|
|
end
|
|
|
|
local function matches(d)
|
|
for _, m in ipairs(matchers) do
|
|
if m(d) then return true end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local _, dirs1 = file.Find("*", "LUA")
|
|
local _, dirs2 = file.Find("lua/*", "GAME")
|
|
|
|
for _, d in ipairs(Merge(dirs1, dirs2)) do
|
|
if matches(d) and not IsExcluded(d) then
|
|
table.insert(roots, d)
|
|
end
|
|
end
|
|
|
|
table.sort(roots)
|
|
return roots
|
|
end
|
|
|
|
-- ============================================================
|
|
-- Polices client (Poppins)
|
|
-- Créées quel que soit le root, donc hors du flux de chargement.
|
|
-- ============================================================
|
|
|
|
if CLIENT then
|
|
local fonts = {
|
|
{ "bladw_text", "Poppins-SemiBold", 25, 600 },
|
|
{ "bladw_text_Medium", "Poppins-SemiBold", 20, 800 },
|
|
{ "bladw_text_Bold", "Poppins-Bold", 70, 700 },
|
|
{ "bladw_text_Regular", "Poppins-Regular", 35, 400 },
|
|
{ "bladw_text2", "Poppins-SemiBold", 21.5, 800 },
|
|
}
|
|
|
|
for _, f in ipairs(fonts) do
|
|
surface.CreateFont(f[1], {
|
|
font = f[2],
|
|
size = f[3],
|
|
weight = f[4],
|
|
antialias = true,
|
|
})
|
|
end
|
|
end
|
|
|
|
-- ============================================================
|
|
-- Point d'entrée
|
|
-- ============================================================
|
|
|
|
Log(C.title, "")
|
|
Log(C.title, " ╔══════════════════════════════════════╗")
|
|
Log(C.title, " ║ NoCode — Autoloader ║")
|
|
Log(C.title, " ╚══════════════════════════════════════╝")
|
|
|
|
local rootPattern = NoCode.config.root
|
|
|
|
-- root "none" (ou vide/nil) -> loader désactivé, on ne charge rien
|
|
if not rootPattern or rootPattern == "" or rootPattern:lower() == "none" then
|
|
Log(C.warn, " Root = none -> autoloader désactivé, rien n'est chargé.")
|
|
Log(C.dim, "")
|
|
return
|
|
end
|
|
|
|
Log(C.info, " Dossier racine : lua/" .. rootPattern)
|
|
Log(C.dim, "")
|
|
|
|
-- 1) Expédie les bibliothèques partagées au client (rndx, etc.)
|
|
for _, lib in ipairs(NoCode.config.libs or {}) do
|
|
SendFolder(lib)
|
|
end
|
|
|
|
-- 2) Charge chaque module racine correspondant au préfixe
|
|
local roots = FindRootFolders(rootPattern)
|
|
|
|
if #roots == 0 then
|
|
Log(C.warn, (" [!] Aucun module trouvé pour le préfixe : %s"):format(rootPattern))
|
|
else
|
|
for _, root in ipairs(roots) do
|
|
if NoCode.config.verbose then
|
|
Log(C.title, " ▶ " .. root)
|
|
end
|
|
LoadFolder(root)
|
|
end
|
|
end
|
|
|
|
Log(C.dim, "")
|
|
Log(C.title, " ╔══════════════════════════════════════╗")
|
|
if SERVER then
|
|
Log(C.ok, (" ║ ✓ %d chargé(s) / %d envoyé(s) au client"):format(stats.loaded, stats.sent))
|
|
else
|
|
Log(C.ok, (" ║ ✓ %d fichier(s) chargé(s) [CLIENT]"):format(stats.loaded))
|
|
end
|
|
if stats.errors > 0 then
|
|
Log(C.err, (" ║ ✗ %d erreur(s)"):format(stats.errors))
|
|
end
|
|
Log(C.title, " ╚══════════════════════════════════════╝")
|
|
Log(C.dim, "") |