Skip to content

Commit

Permalink
Merge pull request #75 from Qubus0/self_setup
Browse files Browse the repository at this point in the history
Self setup
  • Loading branch information
Qubus0 authored Jan 23, 2023
2 parents c46abe7 + 11db012 commit 2d503e8
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 0 deletions.
98 changes: 98 additions & 0 deletions addons/mod_loader/mod_loader_setup.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
extends SceneTree

const LOG_NAME := "ModLoader:Setup"

const settings := {
"IS_LOADER_SETUP_APPLIED": "application/run/is_loader_setup_applied",
"IS_LOADER_SET_UP": "application/run/is_loader_set_up",
"MOD_LOADER_AUTOLOAD": "autoload/ModLoader",
}

# see: [method ModLoaderUtils.register_global_classes_from_array]
const new_global_classes := [
{
"base": "Resource",
"class": "ModData",
"language": "GDScript",
"path": "res://addons/mod_loader/mod_data.gd"
}, {
"base": "Node",
"class": "ModLoaderUtils",
"language": "GDScript",
"path": "res://addons/mod_loader/mod_loader_utils.gd"
}, {
"base": "Resource",
"class": "ModManifest",
"language": "GDScript",
"path": "res://addons/mod_loader/mod_manifest.gd"
}
]

# IMPORTANT: use the ModLoaderUtils via this variable within this script!
# Otherwise, script compilation will break on first load since the class is not defined.
var modloaderutils: Node = load("res://addons/mod_loader/mod_loader_utils.gd").new()


func _init() -> void:
try_setup_modloader()
change_scene(ProjectSettings.get_setting("application/run/main_scene"))


# Set up the ModLoader, if it hasn't been set up yet
func try_setup_modloader() -> void:
# Avoid doubling the setup work
if is_loader_setup_applied():
modloaderutils.log_info("ModLoader is available, mods can be loaded!", LOG_NAME)
OS.set_window_title("%s (Modded)" % ProjectSettings.get_setting("application/config/name"))
return

setup_modloader()

# If the loader is set up, but the override is not applied yet,
# prompt the user to quit and restart the game.
if is_loader_set_up() and not is_loader_setup_applied():
modloaderutils.log_info("ModLoader is set up, but the game needs to be restarted", LOG_NAME)
OS.alert("The Godot ModLoader has been set up. Restart the game to apply the changes. Confirm to quit.")
ProjectSettings.set_setting(settings.IS_LOADER_SETUP_APPLIED, true)
ProjectSettings.save_custom(modloaderutils.get_override_path())
quit()


# Set up the ModLoader as an autoload and register the other global classes.
# Saved as override.cfg besides the game executable to extend the existing project settings
func setup_modloader() -> void:
modloaderutils.log_info("Setting up ModLoader", LOG_NAME)

# Register all new helper classes as global
modloaderutils.register_global_classes_from_array(new_global_classes)

# Add ModLoader autoload (the * marks the path as autoload)
ProjectSettings.set_setting(settings.MOD_LOADER_AUTOLOAD, "*res://addons/mod_loader/mod_loader.gd")
ProjectSettings.set_setting(settings.IS_LOADER_SET_UP, true)

# The game needs to be restarted first, bofore the loader is truly set up
# Set this here and check it elsewhere to prompt the user for a restart
ProjectSettings.set_setting(settings.IS_LOADER_SETUP_APPLIED, false)

ProjectSettings.save_custom(ModLoaderUtils.get_override_path())
modloaderutils.log_info("ModLoader setup complete", LOG_NAME)


func is_loader_set_up() -> bool:
return is_project_setting_true(settings.IS_LOADER_SET_UP)


func is_loader_setup_applied() -> bool:
if not root.get_node_or_null("/root/ModLoader") == null:
if not is_project_setting_true(settings.IS_LOADER_SETUP_APPLIED):
modloaderutils.log_info("ModLoader is already set up. No self setup required.", LOG_NAME)
return true
return false


static func is_project_setting_true(project_setting: String) -> bool:
return ProjectSettings.has_setting(project_setting) and\
ProjectSettings.get_setting(project_setting)



53 changes: 53 additions & 0 deletions addons/mod_loader/mod_loader_utils.gd
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,20 @@ static func get_local_folder_dir(subfolder: String = "") -> String:
return game_install_directory.plus_file(subfolder)


# Get the path where override.cfg will be stored.
# Not the same as the local folder dir (for mac)
static func get_override_path() -> String:
var base_path := ""
if OS.has_feature("editor"):
base_path = ProjectSettings.globalize_path("res://")
else:
# this is technically different to res:// in macos, but we want the
# executable dir anyway, so it is exactly what we need
base_path = OS.get_executable_path().get_base_dir()

return base_path.plus_file("override.cfg")


# Provide a path, get the file name at the end of the path
static func get_file_name_from_path(path: String, make_lower_case := true, remove_extension := false) -> String:
var file_name := path.get_file()
Expand Down Expand Up @@ -223,6 +237,45 @@ static func get_json_string_as_dict(string: String) -> Dictionary:
return parsed.result


# Register an array of classes to the global scope, since Godot only does that in the editor.
# Format: { "base": "ParentClass", "class": "ClassName", "language": "GDScript", "path": "res://path/class_name.gd" }
# You can find these easily in the project.godot file under "_global_script_classes"
# (but you should only include classes belonging to your mod)
static func register_global_classes_from_array(new_global_classes: Array) -> void:
var registered_classes: Array = ProjectSettings.get_setting("_global_script_classes")
var registered_class_icons: Dictionary = ProjectSettings.get_setting("_global_script_class_icons")

for new_class in new_global_classes:
if not is_valid_global_class_dict(new_class):
continue
if registered_classes.has(new_class):
continue

registered_classes.append(new_class)
registered_class_icons[new_class.class] = "" # empty icon, does not matter

ProjectSettings.set_setting("_global_script_classes", registered_classes)
ProjectSettings.set_setting("_global_script_class_icons", registered_class_icons)
ProjectSettings.save_custom(get_override_path())


# Checks if all required fields are in the given [Dictionary]
# Format: { "base": "ParentClass", "class": "ClassName", "language": "GDScript", "path": "res://path/class_name.gd" }
static func is_valid_global_class_dict(global_class_dict: Dictionary) -> bool:
var required_fields := ["base", "class", "language", "path"]
if not global_class_dict.has_all(required_fields):
log_fatal("Global class to be registered is missing one of %s" % required_fields, LOG_NAME)
return false

var file = File.new()
if not file.file_exists(global_class_dict.path):
log_fatal('Class "%s" to be registered as global could not be found at given path "%s"' %
[global_class_dict.class, global_class_dict.path], LOG_NAME)
return false

return true


# Get a flat array of all files in the target directory. This was needed in the
# original version of this script, before becoming deprecated. It may still be
# used if DEBUG_ENABLE_STORING_FILEPATHS is true.
Expand Down

0 comments on commit 2d503e8

Please sign in to comment.