if string.match(debug.getinfo(1).source, '=%[SMODS %w+ ".+"]') then error("Please update your steamodded thanks") end local util = require("debugplus.util") local loggerSucc, logger = pcall(require, "debugplus.logger") local global = {} if not loggerSucc then -- To handle older lovely versions, where I can't properly load my deps. return { getValue = function() -- Can't error myself because it's not propagated, so I error in the first function that is called. error("DebugPlus couldn't load a required component. Please make sure your lovely is up to date.\nYou can grab the latest lovely at: https://github.com/ethangreen-dev/lovely-injector/releases\n\n".. (logger or "No further info")) end } end local configDefinition = { debugMode = { label = "Debug Mode", type = "toggle", default = true, info = {"Toggles everything in DebugPlus except the console."}, onUpdate = function(v) _RELEASE_MODE = not v end }, ctrlKeybinds = { label = util.ctrlText .. " for Keybinds", type = "toggle", default = true, info = {"Requires you hold ".. util.ctrlText .. " when pressing the built in keybinds."} }, logLevel = { label = "Log Level", type = "select", default = "INFO", values = {"ERROR", "WARN", "INFO", "DEBUG"}, info = { "Only shows you logs of a certain level. This setting ignore command logs.", "Will show all logs for the selected level and higher." }, -- Most of the time I wouldn't define onUpdate here for something in another module, -- but I need to avoid circular dependencies and want the logger here. onUpdate = function(v) for k, v in pairs(logger.levelMeta) do v.shouldShow = false end logger.levelMeta.ERROR.shouldShow = true if v == "ERROR" then return logger.handleLogsChange() end logger.levelMeta.WARN.shouldShow = true if v == "WARN" then return logger.handleLogsChange() end logger.levelMeta.INFO.shouldShow = true if v == "INFO" then return logger.handleLogsChange() end logger.levelMeta.DEBUG.shouldShow = true logger.handleLogsChange() end }, showNewLogs = { label = "Show New Logs", type = "toggle", default = true, info = { "Show a message when something is logged. Can also press shift + / to temporarily toggle." } }, onlyCommands = { label = "Only Show Commands", type = "toggle", default = false, info = {"Do not show any logs, other than ones from commands or from you pressing debug keybinds."} }, showHUD = { label = "Show Debug HUD", type = "toggle", default = true, info = {"Shows some debug information on the top left of the screen."} }, -- Hidden config for rn. Even though they are hidden at the default logging level, -- they are so frequent is starts clearing normal logs if left run for a bit. -- This option was added so if someone does want them, they can be reenabled. enableLongDT = { label = "Enable Long DT Messages", type = "toggle", default = false, info = {} }, processTables = { label = "Automatically Expand Printed Tables", type = "toggle", default = true, info = {"When a table is printed, expand it's contents (like in the eval command) instead of just strigifying it."} }, stringifyPrint = { label = "Process Arguments Before Logging", type = "toggle", default = true, info = { "When this is enabled and something is printed to the lovely console/log DebugPlus will handle the processing of the args before logging them.", "This allows the 'Automatically Expand Printed Tables' option to also show up in those logs." } }, hyjackErrorHandler = { label = "Console In Crash Handler", type = "toggle", default = true, info = { "When this is toggled, DebugPlus's console will be accessible in the error handler.", "Requires Steamodded (or another tool to replace the error handler) to function", "Requires a restart for the toggle to take effect" } } } global.configDefinition = configDefinition local configPages = { -- TODO: implement paging, maybe only when I need to { name = "Console", "showNewLogs", "onlyCommands", "logLevel", "processTables", "stringifyPrint", "hyjackErrorHandler", }, { name = "Misc", "debugMode", "ctrlKeybinds", "showHUD", } } for k,v in pairs(configDefinition) do v.key = k end local testValues = {} local configTypes local configMemory local function parseConfigValue(val) val = util.trim(val) if val == "true" then return true end if val == "false" then return false end if val:sub(1, 1) == '"' and val:sub(#val) == '"' then return val:sub(2, #val - 1):gsub("\\(.?)", { ["\\"] = "\\", n = "\n", r = "\r" }) end if tonumber(val) then return tonumber(val) end return { type = "raw", val = val } end local function stringifyConfigValue(val) if val == true then return "true" end if val == false then return "false" end if type(val) == "string" then return '"' .. val:gsub("\\", "\\\\"):gsub("\n", "\\n"):gsub("\r", "\\r") .. '"' end if type(val) == "number" then return string.format("%g", val) end if val.type == "raw" then return val.val end end local function parseConfigFile(data) local t = {} for str in string.gmatch(data, "([^\n\r]+)") do local name, val = str:match("(%w+)%s*=%s*(.+)") if not name then logger.errorLog("Failed to parse line:", str) else t[name] = parseConfigValue(val) end end return t end local function stringifyConfigFile(data) local str = "" for k, v in pairs(data) do local val = stringifyConfigValue(v) if val then str = str .. k .. "=" .. val .. "\n" end end return str end local function loadSaveFromFile() local content = love.filesystem.read("config/DebugPlus.jkr") if not content then return {} end local success, res = pcall(parseConfigFile, content) if success and type(res) == "table" then return res end logger.errorLog("Loading save err", res) return {} end local function generateSaveFileTable() if not configMemory then return loadSaveFromFile() end local fin = {} for k, v in pairs(configMemory) do fin[k] = v.store end return fin end local function updateSaveFile() local conf = generateSaveFileTable() love.filesystem.createDirectory("config") local success, res = pcall(stringifyConfigFile, conf) if success then love.filesystem.write("config/DebugPlus.jkr", res) else logger.errorLog("Failure saving config", res) end end function global.setValue(key, value) local def = configDefinition[key] if not def then return end if configTypes[def.type] and configTypes[def.type].validate then if not configTypes[def.type].validate(value, def) then logger.errorLog('Value for saving key ' .. key .. ' failed to validate') return end end local mem = configMemory[key] mem.store = value mem.value = value if def.onUpdate then def.onUpdate(value) end updateSaveFile() end function global.clearValue(key) local def = configDefinition[key] if not def then return end local mem = configMemory[key] mem.store = nil mem.value = def.default if def.onUpdate then def.onUpdate(value) end updateSaveFile() end function global.getValue(key) local def = configDefinition[key] if not def then return end return configMemory[key].value end configTypes = { toggle = { validate = function(data, def) return type(data) == "boolean" end, render = function(def) return create_toggle({ label = def.label, ref_table = configMemory[def.key], ref_value = "value", callback = function(v) global.setValue(def.key, v) end, info = def.info }) end }, select = { validate = function(data, def) return util.hasValue(def.values, data) end, render = function(def) local curr = util.hasValue(def.values, configMemory[def.key].value) or 1 return create_option_cycle({ options = def.values, current_option = curr, scale = 0.8, opt_callback = "DP_conf_select_callback", label = def.label, info = def.info, dp_key = def.key }) end }, } local function getDefaultsObject() local config = {} for k, v in pairs(configDefinition) do config[k] = v.default end return config end local function generateMemory() local defaults = getDefaultsObject() local loaded = loadSaveFromFile() configMemory = {} for k, v in pairs(loaded) do local store = v local value = nil local def = configDefinition[k] if def then if configTypes[def.type] and configTypes[def.type].validate then if configTypes[def.type].validate(v, def) then value = v else logger.errorLog('Value for saved key ' .. k .. ' failed to validate') value = def.default end else value = v end if def.onUpdate then def.onUpdate(value) end end configMemory[k] = { store = store, value = value, } end for k, v in pairs(defaults) do if configMemory[k] then goto continue end configMemory[k] = { store = nil, value = v, } ::continue:: end end function global.generateConfigTab(arg) local index = arg.index or 1 function G.FUNCS.DP_conf_select_callback(e) global.setValue(e.cycle_config.dp_key, e.to_val) end local nodes = {} for k,v in ipairs(configPages[index]) do local def = configDefinition[v] table.insert(nodes, configTypes[def.type].render(def)) end return { -- ROOT NODE n = G.UIT.ROOT, config = {r = 0.1, minw = 7, minh = 5, align = "cm", padding = arg.source == "lovely" and .05 or .5, colour = arg.source == "lovely" and G.C.CLEAR or G.C.BLACK}, nodes = { { -- COLUMN NODE TO ALIGN EVERYTHING INSIDE VERTICALLY n = G.UIT.C, config = {align = "tm", padding = 0.1}, nodes = nodes } } } end function global.generateConfigTabs(source) local tab = {} for i,v in ipairs(configPages) do table.insert(tab, { label = v.name, tab_definition_function = global.generateConfigTab, tab_definition_function_args = {source = source, index = i } }) end return tab end function global.fakeConfigTab() local tabs = global.generateConfigTabs("lovely") tabs[1].chosen = true G.FUNCS.overlay_menu({ definition = create_UIBox_generic_options({ back_func = "settings", contents = {create_tabs({ snap_to_nav = true, -- colour = {.65, .36, 1, 1}, tabs = tabs, tab_h = 7.05, tab_alignment = 'tm', })} }) }) return {} end generateMemory() -- if debug.getinfo(1).source:match("@.*") then -- For when running under watch -- logger.log("DebugPlus config in watch") -- return global.generateConfigTab() -- For watch config_tab -- end return global