local mod_directory = "./../mods" -- Mod initialization code -- local debug = debug local io = io local ffi = ffi local assert = assert local ipairs = ipairs local loadstring = loadstring local pairs = pairs local pcall = pcall local print = print local rawget = rawget local rawset = rawset local select = select local setmetatable = setmetatable local string = string local table = table local tonumber = tonumber local tostring = tostring Mods = { file = {}, message = {}, lua = { debug = debug, io = io, ffi = ffi, loadstring = loadstring, os = os } } local chat_sound = "wwise/events/ui/play_ui_click" local notify = function(message) local event_manager = Managers and Managers.event if event_manager then event_manager:trigger("event_add_notification_message", "default", message, nil, chat_sound) end print(message) end Mods.message.notify = notify local echo = function(message, sender) local chat_manager = Managers and Managers.chat local event_manager = Managers and Managers.event if chat_manager and event_manager then local message_obj = { message_body = message, is_current_user = false, } local participant = { displayname = sender or "SYSTEM", } local message_sent = false local channel_handle, channel = next(chat_manager:connected_chat_channels()) if channel then event_manager:trigger("chat_manager_message_recieved", channel_handle, participant, message_obj) message_sent = true end if not message_sent then notify(message) return end end print(message) end Mods.message.echo = echo local get_file_path = function(local_path, file_name, file_extension) local file_path = mod_directory if local_path and local_path ~= "" then file_path = file_path .. "/" .. local_path end if file_name and file_name ~= "" then file_path = file_path .. "/" .. file_name end if file_extension and file_extension ~= "" then file_path = file_path .. "." .. file_extension else file_path = file_path .. ".lua" end if string.find(file_path, "\\") then file_path = string.gsub(file_path, "\\", "/") end return file_path end local function read_or_execute(file_path, args, return_type) local f = io.open(file_path, "r") local result if return_type == "lines" then result = {} for line in f:lines() do if line then -- Trim whitespace line = line:gsub("^%s*(.-)%s*$", "%1") -- Handle empty lines and single-line comments if line ~= "" and line:sub(1, 2) ~= "--" then table.insert(result, line) end end end else result = f:read("*all") -- Either execute the data or leave it unmodified if return_type == "exec_result" or return_type == "exec_boolean" then local func = loadstring(result, file_path) result = func(args) end end f:close() if return_type == "exec_boolean" then return true else return result end end local function handle_io(local_path, file_name, file_extension, args, safe_call, return_type) local file_path = get_file_path(local_path, file_name, file_extension) print("[Mod] Loading " .. file_path) -- Check for the existence of the path local ff, err_io = io.open(file_path, "r") if ff ~= nil then ff:close() -- Initialize variables local status, result -- If this is a safe call, wrap it in a pcall if safe_call then status, result = pcall(function () return read_or_execute(file_path, args, return_type) end) -- If status is failed, notify the user and return false if not status then notify("[Mod] Error processing '" .. file_path .. "': " .. tostring(result)) return false end -- If this isn't a safe call, load without a pcall else result = read_or_execute(file_path, args, return_type) end return result -- If the initial open failed, report failure else notify("[Mod] Error opening '" .. file_path .. "': " .. tostring(err_io)) return false end end local function exec(local_path, file_name, file_extension, args) return handle_io(local_path, file_name, file_extension, args, true, "exec_boolean") end Mods.file.exec = exec local function exec_unsafe(local_path, file_name, file_extension, args) return handle_io(local_path, file_name, file_extension, args, false, "exec_boolean") end Mods.file.exec_unsafe = exec_unsafe local function exec_with_return(local_path, file_name, file_extension, args) return handle_io(local_path, file_name, file_extension, args, true, "exec_result") end Mods.file.exec_with_return = exec_with_return local function exec_unsafe_with_return(local_path, file_name, file_extension, args) return handle_io(local_path, file_name, file_extension, args, false, "exec_result") end Mods.file.exec_unsafe_with_return = exec_unsafe_with_return local function mod_dofile(file_path, args) return handle_io(file_path, nil, nil, args, true, "exec_result") end Mods.file.dofile = mod_dofile local function read_content(file_path, file_extension) return handle_io(file_path, nil, file_extension, nil, true, "data") end Mods.file.read_content = read_content local function read_content_to_table(file_path, file_extension) return handle_io(file_path, nil, file_extension, nil, true, "lines") end Mods.file.read_content_to_table = read_content_to_table local file_exists = function(name) print(name) local f = io.open(name,"r") if f ~= nil then print("[Mod]: File exists") io.close(f) return true else print("[Mod]: File does not exist") return false end end Mods.file.exists = file_exists -- Load remaining base modules exec("base/function", "require") local init_mod_framework = function() print("[DMF]: Initializing basic mod hook system...") exec("base/function", "hook") -- The mod manager isn't in the bundles, so load our version from the mods folder local ModManager = exec_with_return("base", "mod_manager") -- Initialize mods after loading managers and state_game files Mods.hook.set("Base", "StateRequireScripts._require_scripts", function (req_func, ...) local req_result = req_func(...) Managers.mod = Managers.mod or ModManager:new() -- Update mod manager Mods.hook.set("Base", "StateGame.update", function (func, self, dt, ...) Managers.mod:update(dt) return func(self, dt, ...) end) -- Skip splash view Mods.hook.set("Base", "StateSplash.on_enter", function (func, self, ...) local result = func(self, ...) self._should_skip = true self._continue = true return result end) -- Trigger state change events Mods.hook.set("Base", "GameStateMachine._change_state", function (func, self, ...) local old_state = self._state local old_state_name = old_state and self:current_state_name() if old_state_name then Managers.mod:on_game_state_changed("exit", old_state_name, old_state) end local result = func(self, ...) local new_state = self._state local new_state_name = new_state and self:current_state_name() if new_state_name then Managers.mod:on_game_state_changed("enter", new_state_name, new_state) end return result end) -- Trigger ending state change event Mods.hook.set("Base", "GameStateMachine.destroy", function (func, self, ...) local old_state = self._state local old_state_name = old_state and self:current_state_name() if old_state_name then Managers.mod:on_game_state_changed("exit", old_state_name) end return func(self, ...) end) return req_result end) -- Set crashify modded property Mods.hook.set("Base", "StateRequireScripts._get_is_modded", function () return true end) end -- Original main script Main = Main or {} require("scripts/boot_init") require("scripts/foundation/utilities/class") -- Expose classes at the global table exec("base/function", "class") require("scripts/foundation/utilities/patches") require("scripts/foundation/utilities/settings") require("scripts/foundation/utilities/table") local GameStateMachine = require("scripts/foundation/utilities/game_state_machine") local LocalizationManager = require("scripts/managers/localization/localization_manager") local PackageManager = require("scripts/foundation/managers/package/package_manager") local PackageManagerEditor = require("scripts/foundation/managers/package/package_manager_editor") local ParameterResolver = require("scripts/foundation/utilities/parameters/parameter_resolver") local StateBoot = require("scripts/game_states/state_boot") local StateLoadAudioSettings = require("scripts/game_states/boot/state_load_audio_settings") local StateLoadBootAssets = require("scripts/game_states/boot/state_load_boot_assets") local StateLoadRenderSettings = require("scripts/game_states/boot/state_load_render_settings") local StateRequireScripts = require("scripts/game_states/boot/state_require_scripts") function Main:init() Script.configure_garbage_collection(Script.ACCEPTABLE_GARBAGE, 0.1, Script.MAXIMUM_GARBAGE, 0.5, Script.FORCE_FULL_COLLECT_GARBAGE_LEVEL, 1, Script.MINIMUM_COLLECT_TIME_MS, 0.1, Script.MAXIMUM_COLLECT_TIME_MS, 0.5) ParameterResolver.resolve_command_line() ParameterResolver.resolve_game_parameters() ParameterResolver.resolve_dev_parameters() Application.set_time_step_policy("throttle", DEDICATED_SERVER and 1 / GameParameters.fixed_time_step or 30) if type(GameParameters.window_title) == "string" and GameParameters.window_title ~= "" then Window.set_title(GameParameters.window_title) end local package_manager = LEVEL_EDITOR_TEST and PackageManagerEditor:new() or PackageManager:new() local localization_manager = LocalizationManager:new() local params = { next_state = "StateGame", index_offset = 1, states = { { StateLoadBootAssets, { package_manager = package_manager, localization_manager = localization_manager } }, { StateRequireScripts, { package_manager = package_manager } }, { StateLoadAudioSettings, {} } }, package_manager = package_manager, localization_manager = localization_manager } if PLATFORM == "win32" and not LEVEL_EDITOR_TEST then table.insert(params.states, 1, { StateLoadRenderSettings, {} }) end if LEVEL_EDITOR_TEST then Wwise.load_bank("wwise/world_sound_fx") end self._package_manager = package_manager self._sm = GameStateMachine:new(nil, StateBoot, params, nil, nil, "Main") -- ####################### -- ## Mod intialization ## init_mod_framework() -- ####################### end function Main:update(dt) self._sm:update(dt) end function Main:render() self._sm:render() end function Main:on_reload(refreshed_resources) self._sm:on_reload(refreshed_resources) end function Main:on_close() local should_close = self._sm:on_close() return should_close end function Main:shutdown() Application.force_silent_exit_policy() if rawget(_G, "Crashify") then Crashify.print_property("shutdown", true) end local owns_package_manager = true if rawget(_G, "Managers") and Managers.package then Managers.package:shutdown_has_started() owns_package_manager = false end local on_shutdown = true self._sm:destroy(on_shutdown) if owns_package_manager then self._package_manager:delete() end end function init() Main:init() end function update(dt) Main:update(dt) end function render() Main:render() end function on_reload(refreshed_resources) Main:on_reload(refreshed_resources) end function on_activate(active) print("LUA window => " .. (active and "ACTIVATED" or "DEACTIVATED")) if active and rawget(_G, "Managers") then if Managers.dlc then Managers.dlc:evaluate_consumables() end if Managers.account then Managers.account:refresh_communication_restrictions() end end end function on_close() local should_close = Main:on_close() if should_close then Application.force_silent_exit_policy() if rawget(_G, "Crashify") then Crashify.print_property("shutdown", true) end end return should_close end function shutdown() Main:shutdown() end