Refactor quest diary module loading and dispatch routing

This commit is contained in:
衡凯 2026-06-28 04:08:50 +08:00
parent 631e459606
commit daab95a1c5
5 changed files with 277 additions and 26 deletions

View file

@ -40,26 +40,20 @@ local _, errinfo = pcall(function()
-- 通用模块 -- 通用模块
require("Envir/QuestDiary/util/util") require("Envir/QuestDiary/util/util")
require("Envir/QuestDiary/util/提升基类") require("Envir/QuestDiary/util/提升基类")
Message.dispatch_handler["GmBoxOBJ"] = require("Envir/QuestDiary/util/GM操作台") ModuleLoader = require("Envir/QuestDiary/util/ModuleLoader")
Message.dispatch_handler["NPCGotoMapOBJ"] = require("Envir/QuestDiary/util/挂机地图传送") ModuleLoader.reset()
Message.dispatch_handler["AutoXunHangOBJ"] = require("Envir/QuestDiary/util/自动巡航下图")
ModuleLoader.loadModule("Envir/QuestDiary/util/GM操作台", "system", { register = true })
ModuleLoader.loadModule("Envir/QuestDiary/util/挂机地图传送", "system", { register = true })
ModuleLoader.loadModule("Envir/QuestDiary/util/自动巡航下图", "system", { register = true })
-- 初始化游戏内容所有文件 -- 初始化游戏内容所有文件
for i, v in ipairs(GetFileList(string.format("QuestDiary/%s", "系统类")) or {}) do ModuleLoader.loadSystemDir("QuestDiary/系统类", "Envir/QuestDiary/系统类", "system")
local file = string.format("Envir/QuestDiary/%s/%s", "系统类", Func.splitString(v, ".")[1]) ModuleLoader.loadGameDirs("QuestDiary/游戏功能", "Envir/QuestDiary/游戏功能", "game")
require(file) ModuleLoader.resolvePending()
end
for i, v in ipairs(GetFileList(string.format("QuestDiary/%s", "游戏功能")) or {}) do
local path = string.format("QuestDiary/%s/%s", "游戏功能", v)
for _, file in ipairs(GetFileList(path) or {}) do
local file_str = string.format("Envir/%s/%s", path, Func.splitString(file, ".")[1])
local ssr = require(file_str)
Message.dispatch_handler[ssr._name] = ssr
end
end
ModuleLoader.loadModule("Envir/QuestDiary/util/系统任务", "postStart", { register = true })
require("Envir/QuestDiary/util/系统任务") ModuleLoader.report()
end) end)

View file

@ -41,11 +41,12 @@ function Message.dispatch(actor, msgID, arg1, arg2, arg3, str)
if not msgName then return end if not msgName then return end
local module, method = msgName:match("([^.]*)_(.*)") local module, method = msgName:match("([^.]*)_(.*)")
if not module or not method then return end
if not _G[module] then return end local obj = Message.dispatch_handler[module]
if not obj then return end
-- 白名单校验:只允许 allowFunc 中注册的方法被前端调用 -- 白名单校验:只允许 allowFunc 中注册的方法被前端调用
local obj = _G[module]
if obj.allowFunc then if obj.allowFunc then
local allowed = false local allowed = false
for _, v in ipairs(obj.allowFunc) do for _, v in ipairs(obj.allowFunc) do

View file

@ -45,8 +45,8 @@ function GameEvent.remove(func)
for i = 1, #eventListeners do for i = 1, #eventListeners do
if eventListeners[i][1] == func then if eventListeners[i][1] == func then
table.remove(eventListeners, i) table.remove(eventListeners, i)
if 0 == #listeners[eventName] then if 0 == #_listeners[eventName] then
listeners[eventName] = nil _listeners[eventName] = nil
end end
return return
end end
@ -70,7 +70,7 @@ function GameEvent.removeByNameAndTag(eventName, tag)
end end
if 0 == #eventListeners then if 0 == #eventListeners then
listeners[eventName] = nil _listeners[eventName] = nil
end end
end end
@ -79,7 +79,7 @@ end
function GameEvent.removeByTag(tag) function GameEvent.removeByTag(tag)
assert(tag, "Tag must not be nil") assert(tag, "Tag must not be nil")
for eventName, eventListeners in pairs(_listeners) do for eventName, eventListeners in pairs(_listeners) do
self.removeListenerByNameAndTag(eventName, tag) GameEvent.removeByNameAndTag(eventName, tag)
end end
end end

View file

@ -0,0 +1,256 @@
ModuleLoader = ModuleLoader or {}
ModuleLoader.loaded = ModuleLoader.loaded or {}
ModuleLoader.failed = ModuleLoader.failed or {}
ModuleLoader.skipped = ModuleLoader.skipped or {}
ModuleLoader.pending = ModuleLoader.pending or {}
ModuleLoader.modules = ModuleLoader.modules or {}
ModuleLoader.modulePaths = ModuleLoader.modulePaths or {}
ModuleLoader.phaseStats = ModuleLoader.phaseStats or {}
local function _log(...)
if release_print then
release_print(...)
elseif LOGPrint then
LOGPrint(...)
else
print(...)
end
end
local function _traceback(err)
return tostring(err) .. "\n" .. debug.traceback("", 2)
end
local function _shortError(err)
local msg = tostring(err or "")
local firstLine = msg:match("([^\n\r]+)") or msg
local lineInfo = msg:match("([^\n\r]-%.lua:%d+:%s*[^\n\r]+)")
return lineInfo or firstLine
end
local function _push(list, item)
list[#list + 1] = item
end
local function _recordPhase(phase, key)
local stat = ModuleLoader.phaseStats[phase]
if not stat then
stat = { success = 0, failed = 0, skipped = 0 }
ModuleLoader.phaseStats[phase] = stat
end
stat[key] = (stat[key] or 0) + 1
end
local function _basename(path)
local name = tostring(path or "")
name = name:gsub("\\", "/")
name = name:match("([^/]+)$") or name
name = name:gsub("%.lua$", "")
return name
end
local function _safeName(module, path)
if type(module) == "table" and module._name and module._name ~= "" then
return tostring(module._name)
end
return _basename(path)
end
local function _dependsReady(depends)
if not depends then
return true
end
if type(depends) ~= "table" then
return false, "_depends must be table"
end
for _, name in ipairs(depends) do
if not ModuleLoader.modules[name] then
return false, "missing dependency: " .. tostring(name)
end
end
return true
end
function ModuleLoader.reset()
ModuleLoader.loaded = {}
ModuleLoader.failed = {}
ModuleLoader.skipped = {}
ModuleLoader.pending = {}
ModuleLoader.modules = {}
ModuleLoader.modulePaths = {}
ModuleLoader.phaseStats = {}
end
function ModuleLoader.getFiles(dir)
local ok, files = xpcall(function()
return GetFileList(dir) or {}
end, _traceback)
if not ok then
_push(ModuleLoader.failed, { phase = "scan", path = dir, error = files })
_log("[ModuleLoader] 扫描失败:", dir, files)
return {}
end
table.sort(files, function(a, b)
return tostring(a) < tostring(b)
end)
return files
end
function ModuleLoader.safeRequire(path, phase)
phase = phase or "default"
local ok, module = xpcall(function()
return require(path)
end, _traceback)
if not ok then
_push(ModuleLoader.failed, { phase = phase, path = path, error = module, short = _shortError(module) })
_recordPhase(phase, "failed")
_log("[ModuleLoader] 加载失败:", path, _shortError(module))
return nil, module
end
_push(ModuleLoader.loaded, { phase = phase, path = path, module = module })
_recordPhase(phase, "success")
return module
end
function ModuleLoader.registerModule(module, path, phase, opts)
opts = opts or {}
phase = phase or "default"
if type(module) ~= "table" then
return true
end
if module._enabled == false then
_push(ModuleLoader.skipped, { phase = phase, path = path, reason = "disabled" })
_recordPhase(phase, "skipped")
return false, "disabled"
end
local name = _safeName(module, path)
local ready, reason = _dependsReady(module._depends)
if not ready then
if opts.deferMissing then
return false, reason, "defer"
end
_push(ModuleLoader.skipped, { phase = phase, path = path, name = name, reason = reason })
_recordPhase(phase, "skipped")
_log("[ModuleLoader] 跳过模块:", name, reason)
return false, reason
end
if ModuleLoader.modules[name] then
local err = "duplicate module name: " .. name .. ", old=" .. tostring(ModuleLoader.modulePaths[name]) .. ", new=" .. tostring(path)
_push(ModuleLoader.failed, { phase = phase, path = path, name = name, error = err })
_recordPhase(phase, "failed")
_log("[ModuleLoader] 模块重名:", err)
return false, err
end
if type(module.init) == "function" then
local ok, err = xpcall(function()
module:init()
end, _traceback)
if not ok then
_push(ModuleLoader.failed, { phase = phase, path = path, name = name, error = err, short = _shortError(err) })
_recordPhase(phase, "failed")
_log("[ModuleLoader] 初始化失败:", name, _shortError(err))
return false, err
end
end
ModuleLoader.modules[name] = module
ModuleLoader.modulePaths[name] = path
if Message and Message.dispatch_handler and module._name and module._name ~= "" then
Message.dispatch_handler[module._name] = module
end
return true
end
function ModuleLoader.loadModule(path, phase, opts)
opts = opts or {}
local module, err = ModuleLoader.safeRequire(path, phase)
if not module then
return nil, err
end
if opts.register ~= false then
local ok, reason, code = ModuleLoader.registerModule(module, path, phase, { deferMissing = true })
if not ok and code == "defer" then
_push(ModuleLoader.pending, { phase = phase, path = path, module = module, reason = reason })
end
end
return module
end
function ModuleLoader.resolvePending()
local changed = true
while changed do
changed = false
for i = #ModuleLoader.pending, 1, -1 do
local item = ModuleLoader.pending[i]
local ok = ModuleLoader.registerModule(item.module, item.path, item.phase, { deferMissing = true })
if ok then
table.remove(ModuleLoader.pending, i)
changed = true
end
end
end
for i = #ModuleLoader.pending, 1, -1 do
local item = ModuleLoader.pending[i]
ModuleLoader.registerModule(item.module, item.path, item.phase, { deferMissing = false })
table.remove(ModuleLoader.pending, i)
end
end
function ModuleLoader.loadSystemDir(dir, requirePrefix, phase)
local files = ModuleLoader.getFiles(dir)
for _, file in ipairs(files) do
if tostring(file):match("%.lua$") then
local name = _basename(file)
ModuleLoader.loadModule(requirePrefix .. "/" .. name, phase, { register = true })
end
end
end
function ModuleLoader.loadGameDirs(rootDir, requirePrefix, phase)
local dirs = ModuleLoader.getFiles(rootDir)
for _, dir in ipairs(dirs) do
local subDir = rootDir .. "/" .. dir
local files = ModuleLoader.getFiles(subDir)
for _, file in ipairs(files) do
if tostring(file):match("%.lua$") then
local name = _basename(file)
ModuleLoader.loadModule(requirePrefix .. "/" .. dir .. "/" .. name, phase, { register = true })
end
end
end
end
function ModuleLoader.report()
local success = #ModuleLoader.loaded
local failed = #ModuleLoader.failed
local skipped = #ModuleLoader.skipped
_log("[ModuleLoader] 启动报告: success=", success, " failed=", failed, " skipped=", skipped)
for phase, stat in pairs(ModuleLoader.phaseStats) do
_log("[ModuleLoader] 阶段:", phase, " success=", stat.success or 0, " failed=", stat.failed or 0, " skipped=", stat.skipped or 0)
end
if failed > 0 then
for _, item in ipairs(ModuleLoader.failed) do
_log("[ModuleLoader] 失败模块:", item.phase or "", item.path or "", item.name or "", item.short or _shortError(item.error))
end
end
if skipped > 0 then
for _, item in ipairs(ModuleLoader.skipped) do
_log("[ModuleLoader] 跳过模块:", item.phase or "", item.path or "", item.name or "", item.reason or "")
end
end
end
return ModuleLoader

View file

@ -249,9 +249,9 @@ JueXueXiuLianOBJ.AttackdamageCfg = {
end, end,
[2] = function(actor, target, hitter, magicId, damage, model, Info) [2] = function(actor, target, hitter, magicId, damage, model, Info)
if Player.CheckNPCRange(actor, target, 1) and magicId ~= 26 then if Player.CheckNPCRange(actor, target, 1) and magicId ~= 26 then
if Func.random(5) and not hasbuff(actor, 10000) then if Func.random(100) and not hasbuff(actor, 10000) then
if addbuff(actor, 10000, 5) then if addbuff(actor, 10000, 5) then
-- Func.sendmsg(actor, string.format("[技能绝学]:#70|%s#215|触发,忽视目标100%防御,持续5s.#7", "百步穿杨")) -- Func.sendmsg(actor, string.format("[技能绝学]:#70|%s#215|触发,忽视目标100%%防御,持续5s.#7", "百步穿杨"))
end end
end end
end end
@ -260,7 +260,7 @@ JueXueXiuLianOBJ.AttackdamageCfg = {
---* 使用刺杀剑术 有5%的概率提升刺杀剑术伤害10% 持续5秒 ---* 使用刺杀剑术 有5%的概率提升刺杀剑术伤害10% 持续5秒
[5] = function(actor, target, hitter, magicId, damage, model, Info) [5] = function(actor, target, hitter, magicId, damage, model, Info)
if magicId == 12 and not Player.CheckNPCRange(actor, target, 1) then if magicId == 12 and not Player.CheckNPCRange(actor, target, 1) then
if Func.random(5) and not hasbuff(actor, 10001) then if Func.random(100) and not hasbuff(actor, 10001) then
addbuff(actor, 10001, 10) addbuff(actor, 10001, 10)
-- Func.sendmsg(actor, string.format("[技能绝学]:#70|%s#215|触发,提升刺杀剑术伤害10%%,持续10s.#7", "撕裂长空")) -- Func.sendmsg(actor, string.format("[技能绝学]:#70|%s#215|触发,提升刺杀剑术伤害10%%,持续10s.#7", "撕裂长空"))
end end