diff --git a/scripts/mods/dml/init.lua b/scripts/mods/dml/init.lua
index 21a16a6..2c729b4 100644
--- a/scripts/mods/dml/init.lua
+++ b/scripts/mods/dml/init.lua
@@ -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
diff --git a/scripts/mods/dml/mod_loader.lua b/scripts/mods/dml/mod_loader.lua
index 812d7fb..4da08a4 100644
--- a/scripts/mods/dml/mod_loader.lua
+++ b/scripts/mods/dml/mod_loader.lua
@@ -59,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
@@ -117,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
@@ -144,29 +138,47 @@ 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<>\n%s\n<>\n<>\n%s\n<>\n<>\n%s\n<>",
+ 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)
end
@@ -181,15 +193,18 @@ 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, ...)
@@ -202,16 +217,25 @@ 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
+ if val.error then
Log.error("ModLoader",
- "<>\n<>\n%s\n<>\n<>\n%s\n<>\n<>\n%s\n<>",
+ "Error: %s\n<>\n%s<>\n<>\n%s<>\n<>\n%s<>",
val.error, val.traceback, val.locals, val.self)
else
Log.error("ModLoader", "Error: %s", val or "[unknown error]")
@@ -230,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,
@@ -240,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)
@@ -267,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)
@@ -287,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)
@@ -353,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