diff --git a/crates/dtmm/assets/mod_main.lua b/crates/dtmm/assets/mod_main.lua index e8a83e3..4422b4a 100644 --- a/crates/dtmm/assets/mod_main.lua +++ b/crates/dtmm/assets/mod_main.lua @@ -1,11 +1,7 @@ -Mods = { - -- Keep a backup of certain system libraries before - -- Fatshark's code scrubs them. - -- The metatable setup prevents mods from overwriting them. - lua = setmetatable({}, { - __index = { io = io, debug = debug, ffi = ffi, os = os }, - }), -} +-- Keep a backup of certain system libraries before +-- Fatshark's code scrubs them. +-- The loader can then decide to pass them on to mods, or ignore them +local libs = { io = io, debug = debug, ffi = ffi, os = os } require("scripts/game_states/boot/state_boot_sub_state_base") local StateBootLoadMods = class("StateBootLoadMods", "StateBootSubStateBase") @@ -18,7 +14,7 @@ StateBootLoadMods.on_enter = function (self, parent, params) self._package_manager = package_manager self._package_handles = { ["packages/mods"] = package_manager:load("packages/mods", "StateBootLoadMods", nil), - ["packages/dmf"] = package_manager:load("packages/dmf", "StateBootLoadMods", nil), + ["packages/dml"] = package_manager:load("packages/dml", "StateBootLoadMods", nil), } end @@ -28,12 +24,12 @@ StateBootLoadMods._state_update = function (self, dt) if state == "load_package" and package_manager:update() then self._state = "load_mods" - local dmf_loader = require("scripts/mods/dmf/dmf_loader") - self._dmf_loader = dmf_loader + local mod_loader = require("scripts/mods/dml/init") + self._mod_loader = mod_loader local mod_data = require("scripts/mods/mod_data") - dmf_loader:init(self._parent.gui, mod_data) - elseif state == "load_mods" and self._dmf_loader:update(dt) then + mod_loader:init(mod_data, libs, self._parent.gui) + elseif state == "load_mods" and self._mod_loader:update(dt) then return true, false end diff --git a/crates/dtmm/assets/mod_manager.lua b/crates/dtmm/assets/mod_manager.lua deleted file mode 100644 index 549a9b6..0000000 --- a/crates/dtmm/assets/mod_manager.lua +++ /dev/null @@ -1,357 +0,0 @@ --- Copyright on this file is owned by Fatshark. --- It is extracted, used and modified with permission only for --- the purpose of loading mods within Warhammer 40,000: Darktide. -local ModManager = class("ModManager") - -local MOD_DATA = require("scripts/mods/mod_data") - -local LOG_LEVELS = { - spew = 4, - info = 3, - warning = 2, - error = 1 -} -local DEFAULT_SETTINGS = { - log_level = LOG_LEVELS.error, - developer_mode = false -} - -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") - -ModManager.init = function(self, boot_gui) - self._mods = {} - self._num_mods = nil - self._state = "not_loaded" - self._settings = Application.user_setting("mod_settings") or DEFAULT_SETTINGS - - self._chat_print_buffer = {} - self._reload_data = {} - self._gui = boot_gui - self._ui_time = 0 - self._network_callbacks = {} - - Crashify.print_property("realm", "modded") - - 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() - return true -end - -ModManager._check_reload = function() - 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 - -- TODO: Use new chat system - -- Managers.chat:add_local_system_message(1, chat_print_buffer[i], true) - - 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 - for i = 1, self._num_mods, 1 do - local mod = self._mods[i] - - if mod and not mod.callbacks_disabled then - self:_run_callback(mod, "update", dt) - 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 handle = self._loading_resource_handle - - if ResourcePackage.has_loaded(handle) then - ResourcePackage.flush(handle) - - local mod = self._mods[self._mod_load_index] - local next_index = mod.package_index + 1 - local mod_data = mod.data - - if next_index > #mod_data.packages then - 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) - else - self:_load_package(mod, next_index) - end - end - 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._start_scan = function(self) - self:print("info", "Starting mod scan") - self._state = "scanning" -end - -ModManager._build_mod_table = function(self) - fassert(table.is_empty(self._mods), "Trying to add mods to non-empty mod table") - - for i, mod_data in ipairs(MOD_DATA) do - printf("[ModManager] mods[%d] = name=%q", i, mod_data.name) - - self._mods[i] = { - id = i, - state = "not_loaded", - callbacks_disabled = false, - name = mod_data.name, - loaded_packages = {}, - packages = mod_data.packages, - } - end - - self._num_mods = #self._mods - - self:print("info", "Found %i mods", #self._mods) -end - -ModManager._load_mod = function(self, index) - self._ui_time = 0 - local mods = self._mods - local mod = mods[index] - - if not mod then - table.clear(self._reload_data) - - return "done" - end - - self:print("info", "loading mod %i", mod.id) - Crashify.print_property("modded", true) - - mod.state = "loading" - - Crashify.print_property(string.format("Mod:%i:%s", mod.id, mod.name), true) - - self._mod_load_index = index - - self:_load_package(mod, 1) - - return "loading" -end - -ModManager._load_package = function(self, mod, index) - mod.package_index = index - local package_name = mod.packages[index] - - if not package_name then - return - end - - self:print("info", "loading package %q", package_name) - - local resource_handle = Application.resource_package(package_name) - self._loading_resource_handle = resource_handle - - ResourcePackage.load(resource_handle) - - mod.loaded_packages[#mod.loaded_packages + 1] = resource_handle -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 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") - - for _, handle in ipairs(mod.loaded_packages) do - ResourcePackage.unload(handle) - Application.release_resource_package(handle) - end - - 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) - self:print("info", "reloading mods") - - 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[i] = self:_run_callback(mod, "on_reload") - else - self:print("info", "not reloading mod, state: %s", mod.state) - end - end - - self:unload_all_mods() - self:_start_scan() - - self._reload_requested = false -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 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.print = function(self, level, str, ...) - local message = string.format("[ModManager][" .. level .. "] " .. 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 - -local function noop() -end - -ModManager.network_bind = noop - -ModManager.network_unbind = noop - -ModManager.network_is_occupied = function() - return false -end - -ModManager.network_send = noop - -ModManager.rpc_mod_user_data = noop - -ModManager.register_network_event_delegate = noop - -ModManager.unregister_network_event_delegate = noop - -ModManager.network_context_created = noop - -return ModManager diff --git a/crates/dtmm/src/engine.rs b/crates/dtmm/src/engine.rs index c994091..a8cab2e 100644 --- a/crates/dtmm/src/engine.rs +++ b/crates/dtmm/src/engine.rs @@ -147,7 +147,7 @@ fn build_mod_data_lua(state: Arc) -> String { for mod_info in state .get_mods() .iter() - .filter(|m| m.get_id() != "dmf" && m.get_enabled()) + .filter(|m| m.get_id() != "dml" && m.get_enabled()) { lua.push_str(" {\n name = \""); lua.push_str(mod_info.get_name()); @@ -419,8 +419,9 @@ pub(crate) async fn deploy_mods(state: State) -> Result<()> { { let mods = state.get_mods(); let first = mods.get(0); - if first.is_none() || !(first.unwrap().get_id() == "dmf" && first.unwrap().get_enabled()) { - eyre::bail!("'Darktide Mod Framework' needs to be installed, enabled and at the top of the load order"); + if first.is_none() || !(first.unwrap().get_id() == "dml" && first.unwrap().get_enabled()) { + // TODO: Add a suggestion where to get it, once that's published + eyre::bail!("'Darktide Mod Loader' needs to be installed, enabled and at the top of the load order"); } }