local logger = require("debugplus.logger") local modtime local global = {} local event local file local running = false local currentType -- For edition type local editionIndex local function genSafeFunc(name, fn) return function(...) if not fn or type(fn) ~= "function" then return end local res = {pcall(fn, ...)} local succ = table.remove(res, 1) if not succ then logger.handleLog({1, 0, 0}, "ERROR", "[Watcher] Center function \"" .. name .. "\" errored:", unpack(res)) return end return unpack(res) end end local function evalLuaFile(content) local fn, err = load(content, "@" .. file) if not fn then logger.handleLog({1, 0, 0}, "ERROR", "[Watcher] Error Loading File:", err) return false end local succ, err = pcall(fn) if not succ then logger.handleLog({1, 0, 0}, "ERROR", "[Watcher] Error Running File:", err) return false end return true, err end local function showTabOverlay(definition, tabName) tabName = tabName or "Tab" return G.FUNCS.overlay_menu({ definition = create_UIBox_generic_options({ contents = {{ n = G.UIT.R, nodes = {create_tabs({ snap_to_nav = true, colour = G.C.BOOSTER, tabs = {{ label = tabName, chosen = true, tab_definition_function = function() return definition end }} })} }} }) }) end local types = { lua = { desc = "Starts watching the lua file provided.", run = function(content) return evalLuaFile(content) end, }, config_tab = { desc = "Starts watching the lua file provided. The returned value is rendered like a config tab (such as the one in SMODS.current_mod.config_tab). Note that invalid tabs will likely crash the game.", run = function(content) local success, res = evalLuaFile(content) if not success then return false end if type(res) ~= "table" or next(res) == nil then logger.handleLog({1, 0, 0}, "ERROR", "[Watcher] Config tab doesn't look valid. Not rendering to prevent a crash. Make sure you're returning something.") return end showTabOverlay(res) end, }, shader = { desc = "Starts watching the the shader file provided. Pops up a ui with a joker to preview the shader on.", check = function() if SMODS and SMODS.Shaders and SMODS.Edition then return true end return false, "Steamodded (v1.0.0~+) is necessary to watch shader files." end, run = function(content) local result, shader = pcall(love.graphics.newShader, content) if not result then return logger.handleLog({1, 0, 0}, "ERROR", "[Watcher] Error Loading Shader:", shader) end local name = content:match("extern [%w_]+ vec2 (%w+);"); if not name then return logger.handleLog({1, 0, 0}, "ERROR", "[Watcher] Could not guess name of shader :/. Not applying to avoid crash.") end G.SHADERS.debugplus_watcher_shader = shader SMODS.Shaders.debugplus_watcher_shader = { original_key = name } if not editionIndex then editionIndex = #G.P_CENTER_POOLS.Edition + 1 end G.P_CENTER_POOLS.Edition[editionIndex] = { key = "e_debugplus_watcher_edition", shader = "debugplus_watcher_shader" } -- Make an area with a joker with our editon local area = CardArea( G.ROOM.T.x + 0.2*G.ROOM.T.w/2,G.ROOM.T.h, G.CARD_W, G.CARD_H, {card_limit = 5, type = 'title', highlight_limit = 0, deck_height = 0.75, thin_draw = 1} ) local card = Card(area.T.x + area.T.w/2, area.T.y, G.CARD_W, G.CARD_H, nil, G.P_CENTERS["j_joker"]) card.edition = {debugplus_watcher_edition = true} area:emplace(card) showTabOverlay({ -- ROOT NODE n = G.UIT.ROOT, config = { r = 0.1, minw = 7, minh = 5, align = "tm", padding = 1, colour = G.C.BLACK }, nodes = {{ n = G.UIT.R, config = { align = "cm", padding = 0.07, no_fill = true, scale = 1 }, nodes = {{ n = G.UIT.O, config = { object = area } }} }} }, "Shader Test") return true end, cleanup = function() table.remove(G.P_CENTER_POOLS.Edition, editionIndex) G.SHADERS.debugplus_watcher_shader = nil SMODS.Shaders.debugplus_watcher_shader = nil end }, center = { desc = "Starts watching the lua file provided. The returned table is used to modify the center given in the key value. The table is similar to SMODS.Joker and friends.", check = function() -- Not entirely sure what to all check for here. if SMODS and SMODS.Joker then return true end return false, "Steamodded (v1.0.0~+) is necessary to watch centers." end, run = function(content) local success, res = evalLuaFile(content) if not success then return false end if not res or type(res) ~= "table" then logger.handleLog({1, 0, 0}, "ERROR", "[Watcher] Center config doesn't look correct. Make sure you are returning an object.") return end if not res.key then logger.handleLog({1, 0, 0}, "ERROR", "[Watcher] Center config is missing a key.") return end local center = G.P_CENTERS[res.key] if not center then logger.handleLog({1, 0, 0}, "ERROR", "[Watcher] The key \"" .. res.key .. "\" does not exist. Make sure your object has been loaded and the key is correct (don't forget the object prefix (e.g. j_) and your mod prefix) you can get the key by hovering over your object and then running `eval dp.hovered.config.center.key`.") return end if res.loc_txt then local loc_txt = res.loc_txt local loc = G.localization.descriptions[center.set][res.key] local loc_changed = false if loc_txt.name then if loc_txt.name ~= loc.name then loc_changed = true loc.name = loc_txt.name end end if loc_txt.text then if #loc_txt.text ~= #loc.text then loc_changed = true else for k, v in ipairs(loc_txt.text) do if v ~= loc.text[k] then loc_changed = true break end end end loc.text = loc_txt.text end if loc_changed then init_localization() end end if res.pos then center.pos.x = res.pos.x center.pos.y = res.pos.y end for k,v in pairs(res) do if type(v) ~= "function" then goto finishfunc end center[k] = genSafeFunc(k, v) ::finishfunc:: end return true end, } } local function loadFile() local info = love.filesystem.getInfo(file) or {} local showReloaded = modtime ~= nil if info.modtime == modtime then return end modtime = info.modtime local content = love.filesystem.read(file) local result, subResult = pcall(currentType.run, content) if not result then return logger.handleLog({1, 0, 0}, "ERROR", "[Watcher] Error Running Watcher:", subResult) end if showReloaded and subResult then logger.handleLog({0, 1, 0}, "INFO", "[Watcher] Reloaded") end return true end local function makeEvent() event = Event { blockable = false, blocking = false, pause_force = true, no_delete = true, trigger = "after", delay = .5, timer = "UPTIME", func = function() if not running then return true end loadFile() event.start_timer = false end } end function global.startWatching(_file, _type) if not _file then return nil, "No file" end local info = love.filesystem.getInfo(_file) if not info then return nil, "File doesn't exist" end if not (info.type == "file") then return nil, "Not a regular file" end if not event then makeEvent() end if running and currentType and currentType.cleanup and type(currentType.cleanup) == "function" then currentType.cleanup() end modtime = nil file = _file currentType = types[_type] if not currentType then return nil, "Shit's erroring (no type)" end if currentType.check and type(currentType.check) == "function" then local res, msg = currentType.check(); if not res then return nil, msg or "Pre-check failed!" end end if not running then running = true loadFile() G.E_MANAGER:add_event(event) end return true end function global.stopWatching() running = false if currentType.cleanup and type(currentType.cleanup) == "function" then currentType.cleanup() end end global.types = types; global.subCommandDesc = "" for k,v in pairs(types) do global.subCommandDesc = global.subCommandDesc .. "watch " .. k .. " [file] - " .. (v.desc or "Wilson forgot to make a description for me.") .. "\n" end return global