Compare commits
No commits in common. "master" and "feat/dtmm" have entirely different histories.
5 changed files with 136 additions and 63 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
dml.zip
|
||||
out/
|
||||
|
|
43
README.md
Normal file
43
README.md
Normal file
|
@ -0,0 +1,43 @@
|
|||
### Contains a small set of basic functionality required for loading other mods. It also handles initial setup and contains a mod_load_order.txt file for mod management.
|
||||
|
||||
### Game updates will automatically disable all mods. Re-run "toggle_darktide_mods.bat" to enable them again.
|
||||
|
||||
### This mod does not need to be added to your mod_load_order.txt file.
|
||||
|
||||
## Installation:
|
||||
1. Copy the Darktide Mod Loader files to your game directory and overwrite existing.
|
||||
2. Run the "toggle_darktide_mods.bat" script in your game folder.
|
||||
3. Copy the Darktide Mod Framework files to your "mods" directory (<game folder>/mods) and overwrite existing.
|
||||
3. Install other mods by downloading them from the Nexus site (https://www.nexusmods.com/warhammer40kdarktide) then adding them to "<game folder>/mods/mod_load_order.txt" with a text editor.
|
||||
|
||||
## Disable mods:
|
||||
* Disable individual mods by removing their name from your mods/mod_load_order.txt file.
|
||||
* Run the "toggle_darktide_mods.bat" script at your game folder and choose to unpatch the bundle database to disable all mod loading.
|
||||
|
||||
## Uninstallation:
|
||||
1. Run the "toggle_darktide_mods.bat" script at your game folder and choose to unpatch the bundle database.
|
||||
2. Delete the mods and tools folders from your game directory.
|
||||
3. Delete the "mod_loader" file from <game folder>/binaries.
|
||||
4. Delete the "9ba626afa44a3aa3.patch_999" file from <game folder>/bundle.
|
||||
|
||||
## Updating the mod loader:
|
||||
1. Run the "toggle_darktide_mods.bat" script at your game folder and choose to unpatch the bundle database.
|
||||
2. Copy the Darktide Mod Loader files to your game directory and overwrite existing (except for mod_load_order.txt, if you wish to preserve your mod list).
|
||||
3. Run "toggle_darktide_mods.bat" at your game folder to re-enable mods.
|
||||
|
||||
## Updating any other mod:
|
||||
1. Delete the mod's directory from your mods folder.
|
||||
2. Extract the updated mod to your mods folder. All settings will remain intact.
|
||||
|
||||
## Troubleshooting:
|
||||
* Make sure your game folder, mods folder, and mod_load_order.txt look like the images on this page: <https://www.nexusmods.com/warhammer40kdarktide/mods/19>
|
||||
* Make sure your mods have their dependencies listed above them in the load order.
|
||||
* Remove all mods from the load order (or add '--' before each line).
|
||||
* If all else fails, re-verify your game files and start the mod installation from the beginning.
|
||||
|
||||
## Creating mods:
|
||||
1. Download the latest Darktide Mod Builder release: <https://github.com/Darktide-Mod-Framework/Darktide-Mod-Builder/releases>.
|
||||
2. Add the unzipped folder to your environment path: <https://www.computerhope.com/issues/ch000549.htm>.
|
||||
3. Run create_mod.bat or "dmb create <mod name>" in the mods folder. This generates a mod folder with the same name.
|
||||
4. Add the new mod name to your mod_load_order.txt.
|
||||
5. Reload mods or restart the game.
|
4
dtmt.cfg
4
dtmt.cfg
|
@ -1,8 +1,8 @@
|
|||
id = "dml"
|
||||
name = "Darktide Mod Loader"
|
||||
summary = "The low-level facilities that enable loading mods from specially prepared bundles."
|
||||
version = "0.1.0"
|
||||
author = "SirAiedail"
|
||||
version = "23.4.05"
|
||||
author = "Darktide Modders"
|
||||
|
||||
categories = [
|
||||
Tools
|
||||
|
|
|
@ -7,13 +7,11 @@ local GameStateMachine = require("scripts/foundation/utilities/game_state_machin
|
|||
|
||||
-- The loader object that is used during game boot
|
||||
-- to initialize the modding environment.
|
||||
local loader = {}
|
||||
local DML = {}
|
||||
|
||||
function loader:init(mod_data, boot_gui)
|
||||
function DML.create_loader(mod_data, boot_gui)
|
||||
local ModLoader = dofile("scripts/mods/dml/mod_loader")
|
||||
local mod_loader = ModLoader:new(mod_data, boot_gui)
|
||||
self._mod_loader = mod_loader
|
||||
Managers.mod = mod_loader
|
||||
|
||||
-- The mod loader needs to remain active during game play, to
|
||||
-- enable reloads
|
||||
|
@ -64,10 +62,11 @@ function loader:init(mod_data, boot_gui)
|
|||
|
||||
return func(self, ...)
|
||||
end)
|
||||
|
||||
return mod_loader
|
||||
end
|
||||
|
||||
function loader:update(dt)
|
||||
local mod_loader = self._mod_loader
|
||||
function DML.update(mod_loader, dt)
|
||||
mod_loader:update(dt)
|
||||
|
||||
local done = mod_loader:all_mods_loaded()
|
||||
|
@ -78,8 +77,8 @@ function loader:update(dt)
|
|||
return done
|
||||
end
|
||||
|
||||
function loader:done()
|
||||
return self._mod_loader:all_mods_loaded()
|
||||
function DML.done(mod_loader)
|
||||
return mod_loader:all_mods_loaded()
|
||||
end
|
||||
|
||||
return loader
|
||||
return DML
|
||||
|
|
|
@ -47,6 +47,10 @@ ModLoader.developer_mode_enabled = function(self)
|
|||
return self._settings.developer_mode
|
||||
end
|
||||
|
||||
ModLoader.set_developer_mode = function(self, enabled)
|
||||
self._settings.developer_mode = enabled
|
||||
end
|
||||
|
||||
ModLoader._draw_state_to_gui = function(self, gui, dt)
|
||||
local state = self._state
|
||||
local t = self._ui_time + dt
|
||||
|
@ -55,13 +59,12 @@ ModLoader._draw_state_to_gui = function(self, gui, dt)
|
|||
|
||||
if state == "scanning" then
|
||||
status_str = "Scanning for mods"
|
||||
elseif state == "loading" then
|
||||
elseif state == "loading" or state == "initializing" then
|
||||
local mod = self._mods[self._mod_load_index]
|
||||
status_str = string.format("Loading mod %q", mod.name)
|
||||
end
|
||||
|
||||
local msg = status_str .. string.rep(".", (2 * t) % 4)
|
||||
Log.info("ModLoader", msg)
|
||||
ScriptGui.text(gui, msg, FONT_MATERIAL, 25, Vector3(20, 30, 1), Color.white())
|
||||
end
|
||||
|
||||
|
@ -113,24 +116,19 @@ ModLoader.update = function(self, dt)
|
|||
self._reload_requested = true
|
||||
end
|
||||
|
||||
if self._reload_requested and self._state == "done" then
|
||||
if self._reload_requested and old_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
|
||||
if old_state == "done" then
|
||||
self:_run_callbacks("update", dt)
|
||||
elseif old_state == "scanning" then
|
||||
Log.info("ModLoader", "Scanning for mods")
|
||||
self:_build_mod_table()
|
||||
|
||||
self._state = self:_load_mod(1)
|
||||
self._ui_time = 0
|
||||
elseif self._state == "loading" then
|
||||
elseif old_state == "loading" then
|
||||
local handle = self._loading_resource_handle
|
||||
|
||||
if ResourcePackage.has_loaded(handle) then
|
||||
|
@ -140,31 +138,49 @@ ModLoader.update = function(self, dt)
|
|||
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 = xpcall(mod_data.run, Script.callstack)
|
||||
|
||||
if not ok then
|
||||
Log.error("ModLoader", "Failed 'run' for %q: %s", mod.name, object)
|
||||
end
|
||||
|
||||
mod.object = object or {}
|
||||
|
||||
self:_run_callback(mod, "init", self._reload_data[mod.id])
|
||||
|
||||
Log.info("ModLoader", "Finished loading %q", mod.name)
|
||||
|
||||
self._state = self:_load_mod(self._mod_load_index + 1)
|
||||
else
|
||||
if next_index <= #mod_data.packages then
|
||||
self:_load_package(mod, next_index)
|
||||
else
|
||||
self._state = "initializing"
|
||||
end
|
||||
end
|
||||
elseif old_state == "initializing" then
|
||||
local mod = self._mods[self._mod_load_index]
|
||||
local mod_data = mod.data
|
||||
|
||||
Log.info("ModLoader", "Initializing mod %q", mod.name)
|
||||
|
||||
mod.state = "running"
|
||||
local ok, object = xpcall(mod_data.run, function(err)
|
||||
if type(err) == "string" then
|
||||
return err .. "\n" .. Script.callstack()
|
||||
else
|
||||
return err
|
||||
end
|
||||
end)
|
||||
|
||||
if not ok then
|
||||
if object.error then
|
||||
object = string.format(
|
||||
"%s\n<<Lua Stack>>\n%s\n<</Lua Stack>>\n<<Lua Locals>>\n%s\n<</Lua Locals>>\n<<Lua Self>>\n%s\n<</Lua Self>>",
|
||||
object.error, object.traceback, object.locals, object.self)
|
||||
end
|
||||
|
||||
Log.error("ModLoader", "Failed 'run' for %q: %s", mod.name, object)
|
||||
end
|
||||
|
||||
mod.object = object or {}
|
||||
|
||||
self:_run_callback(mod, "init", self._reload_data[mod.id])
|
||||
|
||||
Log.info("ModLoader", "Finished loading %q", mod.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)
|
||||
self:_draw_state_to_gui(gui, dt)
|
||||
end
|
||||
|
||||
if old_state ~= self._state then
|
||||
|
@ -177,18 +193,21 @@ ModLoader.all_mods_loaded = function(self)
|
|||
end
|
||||
|
||||
ModLoader.destroy = function(self)
|
||||
self:_run_callbacks("on_destroy")
|
||||
self:unload_all_mods()
|
||||
end
|
||||
|
||||
ModLoader._run_callbacks = function(self, callback_name, ...)
|
||||
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_destroy")
|
||||
self:_run_callback(mod, callback_name, ...)
|
||||
end
|
||||
end
|
||||
|
||||
self:unload_all_mods()
|
||||
end
|
||||
|
||||
ModLoader._run_callback = function (self, mod, callback_name, ...)
|
||||
ModLoader._run_callback = function(self, mod, callback_name, ...)
|
||||
local object = mod.object
|
||||
local cb = object[callback_name]
|
||||
|
||||
|
@ -198,14 +217,26 @@ ModLoader._run_callback = function (self, mod, callback_name, ...)
|
|||
|
||||
local args = table_pack(...)
|
||||
|
||||
local success, val = xpcall(function() return cb(object, table_unpack(args)) end, Script.callstack)
|
||||
local success, val = xpcall(
|
||||
function() return cb(object, table_unpack(args)) end,
|
||||
function(err)
|
||||
if type(err) == "string" then
|
||||
return err .. "\n" .. Script.callstack()
|
||||
else
|
||||
return err
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
if success then
|
||||
return val
|
||||
else
|
||||
Log.error("ModLoader", "Failed to run callback %q for mod %q with id %q. Disabling callbacks until reload.", callback_name, mod.name, mod.id)
|
||||
if type(val) == "table" then
|
||||
Log.error("ModLoader", "<<Script Error>>%s<</Script Error>>\n<<Lua Stack>>\n%s\n<</Lua Stack>>\n<<Lua Locals>>\n%s\n<</Lua Locals>>\n<<Lua Self>>\n%s\n<</Lua Self>>", val.error, val.traceback, val.locals, val.self)
|
||||
Log.error("ModLoader", "Failed to run callback %q for mod %q with id %q. Disabling callbacks until reload.",
|
||||
callback_name, mod.name, mod.id)
|
||||
if val.error then
|
||||
Log.error("ModLoader",
|
||||
"Error: %s\n<<Lua Stack>>\n%s<</Lua Stack>>\n<<Lua Locals>>\n%s<</Lua Locals>>\n<<Lua Self>>\n%s<</Lua Self>>",
|
||||
val.error, val.traceback, val.locals, val.self)
|
||||
else
|
||||
Log.error("ModLoader", "Error: %s", val or "[unknown error]")
|
||||
end
|
||||
|
@ -223,7 +254,8 @@ ModLoader._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(self._mod_data) do
|
||||
Log.info("ModLoader", "mods[%d] = id=%q | name=%q", i, mod_data.id, mod_data.name)
|
||||
Log.info("ModLoader", "mods[%d] = id=%q | name=%q | bundled=%s", i, mod_data.id, mod_data.name,
|
||||
tostring(mod_data.bundled))
|
||||
|
||||
self._mods[i] = {
|
||||
id = mod_data.id,
|
||||
|
@ -233,12 +265,13 @@ ModLoader._build_mod_table = function(self)
|
|||
loaded_packages = {},
|
||||
packages = mod_data.packages,
|
||||
data = mod_data,
|
||||
bundled = mod_data.bundled or false,
|
||||
}
|
||||
end
|
||||
|
||||
self._num_mods = #self._mods
|
||||
|
||||
Log.info("ModLoader", "Found %i mods", #self._mods)
|
||||
Log.info("ModLoader", "Found %i mods", self._num_mods)
|
||||
end
|
||||
|
||||
ModLoader._load_mod = function(self, index)
|
||||
|
@ -260,9 +293,12 @@ ModLoader._load_mod = function(self, index)
|
|||
|
||||
self._mod_load_index = index
|
||||
|
||||
self:_load_package(mod, 1)
|
||||
|
||||
return "loading"
|
||||
if mod.bundled and mod.packages[1] then
|
||||
self:_load_package(mod, 1)
|
||||
return "loading"
|
||||
else
|
||||
return "initializing"
|
||||
end
|
||||
end
|
||||
|
||||
ModLoader._load_package = function(self, mod, index)
|
||||
|
@ -280,7 +316,7 @@ ModLoader._load_package = function(self, mod, index)
|
|||
|
||||
ResourcePackage.load(resource_handle)
|
||||
|
||||
mod.loaded_packages[#mod.loaded_packages + 1] = resource_handle
|
||||
table.insert(mod.loaded_packages, resource_handle)
|
||||
end
|
||||
|
||||
ModLoader.unload_all_mods = function(self)
|
||||
|
@ -346,13 +382,7 @@ end
|
|||
|
||||
ModLoader.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
|
||||
self:_run_callbacks("on_game_state_changed", status, state_name, state_object)
|
||||
else
|
||||
Log.warning("ModLoader", "Ignored on_game_state_changed call due to being in state %q", self._state)
|
||||
end
|
||||
|
|
Loading…
Add table
Reference in a new issue