chore: Rework project structure

Initialize as new DTMT-based project.
This commit is contained in:
Lucas Schwiderski 2023-02-22 09:41:28 +01:00
parent f2b1609320
commit 97a29c03f1
Signed by: lucas
GPG key ID: AA12679AAA6DF4D8
13 changed files with 63 additions and 1246 deletions

44
.luacheckrc Normal file
View file

@ -0,0 +1,44 @@
max_line_length = 120
include_files = {
"scripts/",
}
ignore = {
"12.", -- ignore "Setting a read-only global variable/Setting a read-only field of a global variable."
"542", -- disable warnings for empty if branches. These are useful sometime and easy to notice otherwise.
"212/self", -- Disable unused self warnings.
}
std = "+DT"
stds["DT"] = {
read_globals = {
string = { fields = { "split" }},
table = { fields = {
"merge", "table_to_array", "mirror_table", "tostring", "is_empty", "array_to_table", "reverse", "shuffle",
"merge_recursive", "unpack_map", "remove_unordered_items", "append", "mirror_array_inplace", "size", "dump",
"clear_array", "append_varargs", "find", "for_each", "crop", "mirror_array", "set", "create_copy", "clone",
"contains", "add_meta_logging", "table_as_sorted_string_arrays", "clone_instance", "max", "clear", "find_by_key",
}},
math = { fields = {
"ease_exp", "lerp", "polar_to_cartesian", "smoothstep", "easeCubic", "round", "point_is_inside_2d_triangle",
"radians_to_degrees", "circular_to_square_coordinates", "uuid", "easeInCubic", "round_with_precision",
"clamp", "get_uniformly_random_point_inside_sector", "angle_lerp", "ease_out_exp", "rand_normal",
"bounce", "point_is_inside_2d_box", "catmullrom", "clamp_direction", "ease_in_exp", "random_seed",
"sign", "degrees_to_radians", "sirp", "ease_pulse", "cartesian_to_polar", "ease_out_quad",
"easeOutCubic", "radian_lerp", "auto_lerp", "rand_utf8_string", "point_is_inside_oobb",
}},
Managers = { fields = {
"mod", "event", "chat"
}},
Mods = { fields = {
lua = { fields = { "debug", "io", "ffi", "os" }},
"original_require",
"require_store",
}},
"Crashify","Keyboard","Mouse","Application","Color","Quarternion","Vector3","Vector2","RESOLUTION_LOOKUP",
"ModManager", "Utf8", "StateGame", "ResourcePackage", "class", "Gui", "fassert", "printf", "__print", "ffi",
},
}

View file

@ -1,475 +0,0 @@
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

Binary file not shown.

16
dtmt.cfg Normal file
View file

@ -0,0 +1,16 @@
id = "dml"
name = "Darktide Mod Loader"
description = "This is my new mod 'Darktide Mod Loader'!"
version = "0.1.0"
resources = {
init = "scripts/mods/dml/init"
}
packages = [
"packages/dml"
]
depends = [
"dmf"
]

View file

@ -1,22 +0,0 @@
Mods.original_class = Mods.original_class or class
local _G = _G
local rawget = rawget
local rawset = rawset
_G.CLASS = _G.CLASS or setmetatable({}, {
__index = function(_, key)
return key
end
})
class = function(class_name, super_name, ...)
local result = Mods.original_class(class_name, super_name, ...)
if not rawget(_G, class_name) then
rawset(_G, class_name, result)
end
if not rawget(_G.CLASS, class_name) then
rawset(_G.CLASS, class_name, result)
end
return result
end

View file

@ -1,276 +0,0 @@
--[[
Mods Hook v2:
New version with better control
--]]
-- Hook structure
MODS_HOOKS = MODS_HOOKS or {}
MODS_HOOKS_BY_FILE = MODS_HOOKS_BY_FILE or {}
local _loadstring = Mods.lua.loadstring
local item_template = {
name = "",
func = EMPTY_FUNC,
hooks = {},
}
local item_hook_template = {
name = "",
func = EMPTY_FUNC,
enable = false,
exec = EMPTY_FUNC,
}
local Log = Log
local print_log_info = function(mod_name, message)
Log = Log or rawget(_G, "Log")
if Log then
Log._info(mod_name, message)
else
print("[" .. mod_name .. "]: " .. message)
end
end
local print_log_warning = function(mod_name, message)
Log = Log or rawget(_G, "Log")
if Log then
Log._warning(mod_name, message)
else
print("[" .. mod_name .. "]: " .. message)
end
end
Mods.hook = {
--
-- Set hook
--
set = function(mod_name, func_name, hook_func)
local item = Mods.hook._get_item(func_name)
local item_hook = Mods.hook._get_item_hook(item, mod_name)
print_log_info(mod_name, "Hooking " .. func_name)
item_hook.enable = true
item_hook.func = hook_func
Mods.hook._patch()
end,
--
-- Set hook on every instance of the given file
--
set_on_file = function(mod_name, filepath, func_name, hook_func)
-- Add hook create function to list for the file
MODS_HOOKS_BY_FILE[filepath] = MODS_HOOKS_BY_FILE[filepath] or {}
local hook_create_func = function(this_filepath, this_index)
local dynamic_func_name = "Mods.require_store[\"" .. this_filepath .. "\"][" .. tostring(this_index) .. "]." .. func_name
Mods.hook.set(mod_name, dynamic_func_name, hook_func, false)
end
table.insert(MODS_HOOKS_BY_FILE[filepath], hook_create_func)
-- Add the new hook to every instance of the file
local all_file_instances = Mods.require_store[filepath]
if all_file_instances then
for i, item in ipairs(all_file_instances) do
if item then
hook_create_func(filepath, i)
end
end
end
end,
--
-- Enable/Disable hook
--
enable = function(value, mod_name, func_name)
for _, item in ipairs(MODS_HOOKS) do
if item.name == func_name or func_name == nil then
for _, hook in ipairs(item.hooks) do
if hook.name == mod_name then
hook.enable = value
Mods.hook._patch()
end
end
end
end
return
end,
--
-- Enable all hooks on a stored file
--
enable_by_file = function(filepath, store_index)
local all_file_instances = Mods.require_store[filepath]
local file_instance = all_file_instances and all_file_instances[store_index]
local all_file_hooks = MODS_HOOKS_BY_FILE[filepath]
if all_file_hooks and file_instance then
for i, hook_create_func in ipairs(all_file_hooks) do
hook_create_func(filepath, store_index)
end
end
end,
--
-- Remove hook from chain
--
["remove"] = function(func_name, mod_name)
for i, item in ipairs(MODS_HOOKS) do
if item.name == func_name then
if mod_name ~= nil then
for j, hook in ipairs(item.hooks) do
if hook.name == mod_name then
table.remove(item.hooks, j)
Mods.hook._patch()
end
end
else
local item_name = "MODS_HOOKS[" .. tostring(i) .. "]"
-- Restore orginal function
assert(_loadstring(item.name .. " = " .. item_name .. ".func"))()
-- Remove hook function
table.remove(MODS_HOOKS, i)
return
end
end
end
return
end,
--
-- Move hook to front of the hook chain
--
front = function(mod_name, func_name)
for _, item in ipairs(MODS_HOOKS) do
if item.name == func_name or func_name == nil then
for i, hook in ipairs(item.hooks) do
if hook.name == mod_name then
local saved_hook = table.clone(hook)
table.remove(item.hooks, i)
table.insert(item.hooks, saved_hook)
Mods.hook._patch()
end
end
end
end
return
end,
--
-- Get function by function name
--
_get_func = function(func_name)
return assert(_loadstring("return " .. func_name))()
end,
--
-- Get item by function name
--
_get_item = function(func_name)
-- Find existing item
for _, item in ipairs(MODS_HOOKS) do
if item.name == func_name then
return item
end
end
-- Create new item
local item = table.clone(item_template)
item.name = func_name
item.func = Mods.hook._get_func(func_name)
-- Save
table.insert(MODS_HOOKS, item)
return item
end,
--
-- Get item hook by mod name
--
_get_item_hook = function(item, mod_name)
-- Find existing item
for _, hook in ipairs(item.hooks) do
if hook.name == mod_name then
return hook
end
end
-- Create new item
local item_hook = table.clone(item_hook_template)
item_hook.name = mod_name
-- Save
table.insert(item.hooks, 1, item_hook)
return item_hook
end,
--
-- If settings are changed the hook itself needs to be updated
--
_patch = function(mods_hook_item)
for i, item in ipairs(MODS_HOOKS) do
local item_name = "MODS_HOOKS[" .. i .. "]"
local last_j = 1
for j, hook in ipairs(item.hooks) do
local hook_name = item_name .. ".hooks[" .. j .. "]"
local before_hook_name = item_name .. ".hooks[" .. (j - 1) .. "]"
if j == 1 then
if hook.enable then
assert(
_loadstring(
hook_name .. ".exec = function(...)" ..
" return " .. hook_name .. ".func(" .. item_name .. ".func, ...)" ..
"end"
)
)()
else
assert(
_loadstring(
hook_name .. ".exec = function(...)" ..
" return " .. item_name .. ".func(...)" ..
"end"
)
)()
end
else
if hook.enable then
assert(
_loadstring(
hook_name .. ".exec = function(...)" ..
" return " .. hook_name .. ".func(" .. before_hook_name .. ".exec, ...)" ..
"end"
)
)()
else
assert(
_loadstring(
hook_name .. ".exec = function(...)" ..
" return " .. before_hook_name .. ".exec(...)" ..
"end"
)
)()
end
end
last_j = j
end
-- Patch orginal function call
assert(_loadstring(item.name .. " = " .. item_name .. ".hooks[" .. last_j .. "].exec"))()
end
end,
}

View file

@ -1,35 +0,0 @@
Mods.require_store = Mods.require_store or {}
Mods.original_require = Mods.original_require or require
local can_insert = function(filepath, new_result)
local store = Mods.require_store[filepath]
if not store or #store == 0 then
return true
end
if store[#store] ~= new_result then
return true
end
end
require = function(filepath, ...)
local Mods = Mods
local result = Mods.original_require(filepath, ...)
if result and type(result) == "table" then
if can_insert(filepath, result) then
Mods.require_store[filepath] = Mods.require_store[filepath] or {}
local store = Mods.require_store[filepath]
table.insert(store, result)
--print("[Require] #" .. tostring(#store) .. " of " .. filepath)
if Mods.hook then
Mods.hook.enable_by_file(filepath, #store)
end
end
end
return result
end

View file

@ -1,417 +0,0 @@
local ModManager = class("ModManager")
local Keyboard = Keyboard
local BUTTON_INDEX_R = Keyboard.button_index("r")
local BUTTON_INDEX_LEFT_SHIFT = Keyboard.button_index("left shift")
local BUTTON_INDEX_LEFT_CTRL = Keyboard.button_index("left ctrl")
local LOG_LEVELS = {
spew = 4,
info = 3,
warning = 2,
error = 1
}
local string_format = string.format
local function printf(f, ...)
print(string.format(f, ...))
end
local function deepcopy(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[deepcopy(orig_key)] = deepcopy(orig_value)
end
setmetatable(copy, deepcopy(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end
-- Clone the global mod file library to a local table
local _io = deepcopy(Mods.file)
ModManager.init = function (self, boot_gui)
self._mods = {}
self._num_mods = nil
self._state = "not_loaded"
self._settings = Application.user_setting("mod_manager_settings") or {
log_level = 1,
developer_mode = false
}
self._chat_print_buffer = {}
self._reload_data = {}
self._gui = boot_gui
self._ui_time = 0
self._network_callbacks = {}
Crashify.print_property("realm", "modded")
print("[ModManager] Starting mod manager...")
self._mod_metadata = {}
self._state = "scanning"
end
ModManager.developer_mode_enabled = function (self)
return self._settings.developer_mode
end
ModManager._draw_state_to_gui = function (self, gui, dt)
local state = self._state
local t = self._ui_time + dt
self._ui_time = t
local status_str = "Loading mods"
if state == "scanning" then
status_str = "Scanning for mods"
elseif state == "loading" then
local mod = self._mods[self._mod_load_index]
status_str = string.format("Loading mod %q", mod.name)
end
--Gui.text(gui, status_str .. string.rep(".", (2 * t) % 4), "materials/fonts/arial", 16, nil, Vector3(5, 10, 1))
end
ModManager.remove_gui = function (self)
self._gui = nil
end
ModManager._has_enabled_mods = function (self)
return true
end
ModManager._check_reload = function (self)
return Keyboard.pressed(BUTTON_INDEX_R) and
Keyboard.button(BUTTON_INDEX_LEFT_SHIFT) + Keyboard.button(BUTTON_INDEX_LEFT_CTRL) == 2
end
ModManager.update = function (self, dt)
local chat_print_buffer = self._chat_print_buffer
local num_delayed_prints = #chat_print_buffer
if num_delayed_prints > 0 and Managers.chat then
for i = 1, num_delayed_prints, 1 do
Mods.message.echo(chat_print_buffer[i])
chat_print_buffer[i] = nil
end
end
local old_state = self._state
if self._settings.developer_mode and self:_check_reload() then
self._reload_requested = true
end
if self._reload_requested and self._state == "done" then
self:_reload_mods()
end
if self._state == "done" then
if self._num_mods then
for i = 1, self._num_mods, 1 do
local mod = self._mods[i]
if mod and mod.enabled and not mod.callbacks_disabled then
self:_run_callback(mod, "update", dt)
end
end
end
elseif self._state == "scanning" then
self:_build_mod_table()
self._state = self:_load_mod(1)
self._ui_time = 0
elseif self._state == "loading" then
local mod = self._mods[self._mod_load_index]
local mod_data = mod.data
mod.state = "running"
local ok, object = pcall(mod_data.run)
if not ok then
self:print("error", "%s", object)
end
local name = mod.name
mod.object = object or {}
self:_run_callback(mod, "init", self._reload_data[mod.id])
self:print("info", "%s loaded.", name)
self._state = self:_load_mod(self._mod_load_index + 1)
end
local gui = self._gui
if gui then
self:_draw_state_to_gui(gui, dt)
end
if old_state ~= self._state then
self:print("info", "%s -> %s", old_state, self._state)
end
end
ModManager.all_mods_loaded = function (self)
return self._state == "done"
end
ModManager.destroy = function (self)
self:unload_all_mods()
end
ModManager._run_callback = function (self, mod, callback_name, ...)
local object = mod.object
local cb = object[callback_name]
if not cb then
return
end
local success, val = pcall(cb, object, ...)
if success then
return val
else
self:print("error", "%s", val or "[unknown error]")
self:print("error", "Failed to run callback %q for mod %q with id %d. Disabling callbacks until reload.",
callback_name, mod.name, mod.id)
mod.callbacks_disabled = true
end
end
ModManager._build_mod_table = function (self)
fassert(table.is_empty(self._mods), "Trying to add mods to non-empty mod table")
-- Get the mods' load order from mod_load_order file
local mod_load_order = _io.read_content_to_table("mod_load_order", "txt")
if not mod_load_order then
print("ERROR executing mod_load_order: " .. tostring(mod_load_order))
mod_load_order = {}
end
-- Add DMF to the mod load order
table.insert(mod_load_order, 1, "dmf")
-- Read the .mod files of given mods and, if everything's fine, add mods' entries to the mods list.
for i = 1, #mod_load_order do
local mod_name = mod_load_order[i]
self._mods[i] = {
state = "not_loaded",
callbacks_disabled = false,
id = i,
name = mod_name,
enabled = true,
handle = mod_name,
loaded_packages = {}
}
end
self._num_mods = #self._mods
end
ModManager._load_mod = function (self, index)
self._ui_time = 0
local mods = self._mods
local mod = mods[index]
while mod and not mod.enabled do
index = index + 1
mod = mods[index]
end
if not mod then
table.clear(self._reload_data)
return "done"
end
local id = mod.id
local mod_name = mod.name
self:print("info", "loading mod %s", id)
Crashify.print_property("modded", true)
local mod_data = _io.exec_with_return(mod_name, mod_name, "mod")
if not mod_data then
self:print("error", "Mod file is invalid or missing. Mod %q with id %d skipped.", mod.name, mod.id)
mod.enabled = false
return self:_load_mod(index + 1)
end
self:print("spew", "<mod info>\n%s\n</mod info>", mod_data)
mod.data = mod_data
mod.name = mod.name or mod_data.NAME or "Mod " .. mod.id
mod.state = "loading"
Crashify.print_property(string.format("Mod:%s:%s", id, mod.name), true)
self._mod_load_index = index
return "loading"
end
ModManager.unload_all_mods = function (self)
if self._state ~= "done" then
self:print("error", "Mods can't be unloaded, mod state is not \"done\". current: %q", self._state)
return
end
self:print("info", "Unload all mod packages")
for i = self._num_mods, 1, -1 do
local mod = self._mods[i]
if mod and mod.enabled then
self:unload_mod(i)
end
self._mods[i] = nil
end
self._num_mods = nil
self._state = "unloaded"
end
ModManager.unload_mod = function (self, index)
local mod = self._mods[index]
if mod then
self:print("info", "Unloading %q.", mod.name)
self:_run_callback(mod, "on_unload")
mod.state = "not_loaded"
else
self:print("error", "Mod index %i can't be unloaded, has not been loaded", index)
end
end
ModManager._reload_mods = function (self)
for i = 1, self._num_mods, 1 do
local mod = self._mods[i]
if mod and mod.state == "running" then
self:print("info", "reloading %s", mod.name)
self._reload_data[mod.id] = self:_run_callback(mod, "on_reload")
else
self:print("info", "not reloading mod, state: %s", mod.state)
end
end
self:unload_all_mods()
self._state = "scanning"
self._reload_requested = false
Mods.message.notify("Mods reloaded.")
end
ModManager.on_game_state_changed = function (self, status, state_name, state_object)
if self._state == "done" then
for i = 1, self._num_mods, 1 do
local mod = self._mods[i]
if mod and mod.enabled and not mod.callbacks_disabled then
self:_run_callback(mod, "on_game_state_changed", status, state_name, state_object)
end
end
else
self:print("warning", "Ignored on_game_state_changed call due to being in state %q", self._state)
end
end
ModManager._visit = function (self, mod_list, visited, sorted, mod_data)
self:print("debug", "Visiting mod %q with id %d", mod_data.name, mod_data.id)
if visited[mod_data] then
return mod_data.enabled
end
if visited[mod_data] ~= nil then
self:print("error", "Dependency cycle detected at mod %q with id %d", mod_data.name, mod_data.id)
return false
end
visited[mod_data] = false
local enabled = mod_data.enabled or false
for i = 1, mod_data.num_children or 0 do
local child_id = mod_data.children[i]
local child_index = table.find_by_key(mod_list, "id", child_id)
local child_mod_data = mod_list[child_index]
if not child_mod_data then
self:print("warning", "Mod with id %d not found", child_id)
elseif not self:_visit(mod_list, visited, sorted, child_mod_data) and enabled then
self:print("warning", "Disabled mod %q with id %d due to missing dependency %d.",
mod_data.name, mod_data.id, child_id)
enabled = false
end
end
mod_data.enabled = enabled
visited[mod_data] = true
sorted[#sorted + 1] = mod_data
return enabled
end
ModManager.print = function (self, level, str, ...)
local message = string.format("[ModManager][" .. level .. "] " .. tostring(str), ...)
local log_level = LOG_LEVELS[level] or 99
if log_level <= 2 then
print(message)
end
if log_level <= self._settings.log_level then
self._chat_print_buffer[#self._chat_print_buffer + 1] = message
end
end
ModManager.network_bind = function (self)
return
end
ModManager.network_unbind = function (self)
return
end
ModManager.network_is_occupied = function (self)
return false
end
ModManager.network_send = function (self)
return
end
ModManager.rpc_mod_user_data = function (self)
return
end
ModManager.register_network_event_delegate = function (self)
return
end
ModManager.unregister_network_event_delegate = function (self)
return
end
ModManager.network_context_created = function (self)
return
end
return ModManager

View file

@ -1,6 +0,0 @@
-- ################################################################
-- Enter user mod names below, separated by line.
-- Order in the list determines the order in which mods are loaded.
-- Do not rename a mod's folders.
-- You do not need to include 'base' or 'dmf' mod folders.
-- ################################################################

3
packages/dml.package Normal file
View file

@ -0,0 +1,3 @@
lua = [
"scripts/mods/dml/*"
]

View file

@ -1,10 +0,0 @@
@echo off
echo Starting Darktide patcher from %~dp0...
.\tools\dtkit-patch --toggle .\bundle
if errorlevel 1 goto failure
pause
exit
:failure
echo Error patching the Darktide bundle database. See logs.
pause
exit

View file

@ -1,5 +0,0 @@
# dtkit-patch
Simple tool based on Aussiemon's nodejs script for patching `bundle_database.data` to load Darktide mods.
https://github.com/ManShanko/dtkit-patch

Binary file not shown.