--[[ DYNAMIC ZONE Original Author(s) Singustromo Edited by License Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) (https://creativecommons.org/licenses/by-nc-sa/4.0) --]] parent = _G["dynamic_zone"] if not (parent and parent.VERSION and parent.VERSION >= 20241224) then return end local opt = dynamic_zone_mcm local utils = dynamic_zone_utils ------------------------------------------ -- Global Variables & Constants -- ------------------------------------------ CONST_BLOCKSTATECHANGE_NOTIFY_DELAY_MS = 100 CONST_LOGGING_PREFIX = "[DZT]" -- used for console logging valid_logging_channels = { def = "INFO", info = "INFO", warning = "WARNING", error = "ERROR" } registered_logging_channels = { --[[ info = instance, warning = instance, ... --]] } local log_mcm --------------------- -- Callbacks -- --------------------- function on_game_start() RegisterScriptCallback("on_game_load", inject_debug_commands) RegisterScriptCallback("dynzone_changed_block_state", dynzone_changed_block_state) -- Due to this, functors called before creating those instances will log to xray console log_mcm = log_register("info", "Debug") create_logging_instances() end function inject_debug_commands() -- no need, if not enabled. Idk if it would even break something if not (DEV_DEBUG or DEV_DEBUG_DEV) then return end local CMD = debug_cmd_list.command_get_list() function CMD.dynamic_zone(_, __, x) x:SendOutput("/ DYNAMIC ZONE " .. parent.VERSION_STRING) parent.main_routine{ force = true } end function CMD.dynamic_zone_accessible(_, __, x) x:SendOutput("- Reset all route states!") parent.addon_safe_removal() end ui_debug_launcher.inject("action", { name = "[DYNZONE] Trigger", cmd = "dynamic_zone", hide_ui = 2, key = "DIK_D" } ) ui_debug_launcher.inject("action", { name = "[DYNZONE] Clear routes", cmd = "dynamic_zone_accessible", hide_ui = 2 } ) end -- Just informs us about the exact block state -- A bit hacky to prevent recursive referencing function dynzone_changed_block_state(previous, current) if (not opt.get("verbose")) then UnregisterScriptCallback("dynzone_changed_block_state", dynzone_changed_block_state) end -- We only use it in this function, to prevent recursive referencing local route_manager = dynamic_zone_routes -- Done this way, so it shows the updated values changed by other functions utils.timed_call(CONST_BLOCKSTATECHANGE_NOTIFY_DELAY_MS, function(previous, current) local output_str = "Previously Blocked routes:" for route_id, _ in pairs(previous) do output_str = output_str .. "\n" .. route_manager.route_tostring(route_id, true) end if (size_table(previous) > 0) then log_mcm("%s\nA total of %s routes were blocked.", output_str, size_table(previous)) end local output_str = "Newly Blocked routes:" for route_id, _ in pairs(current) do output_str = output_str .. "\n" .. route_manager.route_tostring(route_id, true) end if (size_table(current) > 0) then log_mcm("%s\nA total of %s routes are freshly blocked.", output_str, size_table(current)) end local blocked, discovered = 0, 0 for route_id, route in route_manager.iterate_routes(true) do blocked = (route.blocked) and (blocked +1) or blocked discovered = (route.block_discovered) and (discovered +1) or discovered end log_mcm("A total of %s routes are blocked (%s discovered)", blocked, discovered) return true end, previous, current) end --------------------------- -- Debugging -- --------------------------- -- These are mainly used by this script or others where it is more suited than mcm logging function log(message, ...) if (not opt.get("verbose")) then return end local args = {...} local output = string.format("%s %s", CONST_LOGGING_PREFIX, message) printf(output, unpack(args)) end -- These two are more important, thus are always logged to console. function log_warn(message, ...) local args = {...} local output = string.format("~%s %s", CONST_LOGGING_PREFIX, message) printf(output, unpack(args)) end function log_error(message, ...) local args = {...} local output = string.format("!%s %s", CONST_LOGGING_PREFIX, message) printf(output, unpack(args)) end -- REQUIRES MOD-CONFIGURATION-MENU (MCM) -- We only create logging instances `on_game_start`, so we don't load mcm_log earlier than necessary. function create_logging_instances() if (_g.is_empty(registered_logging_channels)) then return elseif not (mcm_log and mcm_log.new) then log_warn("MCM Logging utility not found. Falling back to console logging.") return end for channelname, value in pairs(registered_logging_channels) do if (type(value) == "userdata") then goto continue end local logger = mcm_log and mcm_log.new and mcm_log.new(channelname) if (not logger) then log_warn("Unable to create a MCM logging instance! Falling back to console logging.") callstack() return end logger.continuous = true logger.enabled = true registered_logging_channels[channelname] = logger :: continue :: end end -- Called like this: e.g. log_info = debug.log_register("info", "Anomalies") -- will only return function that prints to logs, when verbose setting is active -- Will log via the MCM utility, only to console as fallback -- Using string.format to be able to use same rules as ISO C sprintf -- @param channelname logging level - defaults to info, if invalid -- @param identifier optional -- @returns pointer to logging function which uses mcm logging, when available function log_register(channelname, identifier) if (not channelname) then return end identifier = identifier and string.upper(identifier) channelname = valid_logging_channels[string.lower(channelname)] or valid_logging_channels.def registered_logging_channels[channelname] = true local id_prefix = (identifier and type(identifier == "string")) and (" - " .. identifier) or "" return function(message, ...) if (not opt.get("verbose")) then return end local data = { channel = channelname, id = id_prefix } local logger = (type(registered_logging_channels[data.channel]) ~= "boolean") and registered_logging_channels[data.channel] -- Fallback for e.g. before `on_game_start` or if `mcm_log` does not exist if not (logger and logger.log) then printf(string.format(CONST_LOGGING_PREFIX .. message, ...)) return end local default_prefix = logger.prefix if (data.id and data.id ~= "") then logger.prefix = logger.prefix .. data.id end logger:log(string.format(message, ...)) logger.prefix = default_prefix end end