-- ============================================================ -- 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) -- • "none" (ou vide/nil) -> loader désactivé, rien n'est chargé 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" }, } -- ============================================================ -- 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 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 -- ============================================================ -- 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, "")