475 lines
12 KiB
Text
475 lines
12 KiB
Text
local mod_directory = "./../mods"
|
|
|
|
-- Mod initialization code --
|
|
local debug = debug
|
|
local io = io
|
|
local ffi = ffi
|
|
|
|
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
|
|
|
|
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
|