Darktide-Mod-Loader/binaries/mod_loader

475 lines
12 KiB
Text

local mod_directory = "./../mods"
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
-- Mod initialization code --
local debug = rawget(_G, "debug")
local io = rawget(_G, "io")
local ffi = require("ffi")
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