diff --git a/lua/autorun/bladWinRpMenu_loader.lua b/lua/autorun/bladWinRpMenu_loader.lua index 1bf9bd5..48d505a 100644 --- a/lua/autorun/bladWinRpMenu_loader.lua +++ b/lua/autorun/bladWinRpMenu_loader.lua @@ -1,23 +1,267 @@ -xyghtBladWinRPMenu = xyghtBladWinRPMenu or {} +-- ============================================================ +-- AUTOLOADER nocode — Dual-path récursif +-- Fichier : lua/autorun/bladWinRpMenu_loader.lua +-- ============================================================ -AddCSLuaFile("bladw_deathscreen/config/sh_config.lua") +local NoCode = {} -if SERVER then - AddCSLuaFile("bladw_hud/client/cl_hud.lua") - AddCSLuaFile("bladw_cMenu/client/cl_cMenu.lua") - AddCSLuaFile("bladw_deathscreen/client/cl_interface_ds.lua") +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) + root = "bladw_announcement", + -- 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" }, +} - include("bladw_deathscreen/server/sv_hooks.lua") +-- ============================================================ +-- Logs +-- ============================================================ - resource.AddSingleFile("resource/fonts/Poppins-SemiBold.ttf") - resource.AddSingleFile("resource/fonts/Poppins-Medium.ttf") - resource.AddSingleFile("resource/fonts/Poppins-Bold.ttf") - resource.AddSingleFile("resource/fonts/Poppins-Regular.ttf") -else - include("bladw_hud/client/cl_hud.lua") - include("bladw_cMenu/client/cl_cMenu.lua") - include("bladw_deathscreen/client/cl_interface_ds.lua") +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 + +local function FindRootFolders(pattern) + local roots = {} + + -- Construit la fonction de test selon la présence ou non d'un wildcard. + local matches + if pattern:find("*", 1, true) then + local lua_pat = GlobToPattern(pattern) + matches = function(d) return d:match(lua_pat) ~= nil end + else + matches = function(d) return d == pattern end + 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 + +if CLIENT then surface.CreateFont("bladw_text", { font = "Poppins-SemiBold", size = 25, @@ -52,4 +296,48 @@ else weight = 800, antialias = true }) -end \ No newline at end of file + +end + +-- ============================================================ +-- Point d'entrée +-- ============================================================ + +Log(C.title, "") +Log(C.title, " ╔══════════════════════════════════════╗") +Log(C.title, " ║ NoCode — Autoloader ║") +Log(C.title, " ╚══════════════════════════════════════╝") +Log(C.info, " Dossier racine : lua/" .. NoCode.config.root) +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(NoCode.config.root) + +if #roots == 0 then + Log(C.warn, (" [!] Aucun module trouvé pour le préfixe : %s"):format(NoCode.config.root)) +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, "") \ No newline at end of file