mod_loader_store.gd 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. extends Node
  2. # ModLoaderStore
  3. ## Singleton (autoload) for storing data. Should be added before ModLoader,
  4. ## as an autoload called `ModLoaderStore`
  5. # Constants
  6. # =============================================================================
  7. # Most of these settings should never need to change, aside from the DEBUG_*
  8. # options (which should be `false` when distributing compiled PCKs)
  9. const MODLOADER_VERSION := "7.0.1"
  10. # This is where mod ZIPs are unpacked to
  11. const UNPACKED_DIR := "res://mods-unpacked/"
  12. # Default name for the mod hook pack
  13. const MOD_HOOK_PACK_NAME := "mod-hooks.zip"
  14. # Set to true to require using "--enable-mods" to enable them
  15. const REQUIRE_CMD_LINE := false
  16. const LOG_NAME := "ModLoader:Store"
  17. const URL_MOD_STRUCTURE_DOCS := "https://wiki.godotmodding.com/guides/modding/mod_structure"
  18. const MOD_LOADER_DEV_TOOL_URL := "https://github.com/GodotModding/godot-mod-tool"
  19. # Vars
  20. # =============================================================================
  21. # Stores arrays of hook callables that will be applied to a function,
  22. # associated by a hash of the function name and script path
  23. # Example:
  24. # var modding_hooks := {
  25. # 1917482423: [Callable, Callable],
  26. # 3108290668: [Callable],
  27. # }
  28. var modding_hooks := {}
  29. # Stores script paths and method names to be processed for hooks
  30. # Example:
  31. # var hooked_script_paths := {
  32. # "res://game/game.gd": ["_ready", "do_something"],
  33. # }
  34. var hooked_script_paths := {}
  35. # Order for mods to be loaded in, set by `get_load_order`
  36. var mod_load_order := []
  37. # Stores data for every found/loaded mod
  38. var mod_data := {}
  39. # Any mods that are missing their dependancies are added to this
  40. # Example property: "mod_id": ["dep_mod_id_0", "dep_mod_id_2"]
  41. var mod_missing_dependencies := {}
  42. # Set to false after ModLoader._init()
  43. # Helps to decide whether a script extension should go through the _ModLoaderScriptExtension.handle_script_extensions() process
  44. var is_initializing := true
  45. # Store all extenders paths
  46. var script_extensions := []
  47. # Stores scene paths that need to be reloaded from file.
  48. # Used to apply extension to scripts that are attached to preloaded scenes.
  49. var scenes_to_refresh := []
  50. # Dictionary of callables to modify a specific scene.
  51. # Example property: "scene_path": [Callable, Callable]
  52. var scenes_to_modify := {}
  53. # Things to keep to ensure they are not garbage collected (used by `save_scene`)
  54. var saved_objects := []
  55. # Stores all the taken over scripts for restoration
  56. var saved_scripts := {}
  57. # Stores main scripts for mod disabling
  58. var saved_mod_mains := {}
  59. # Stores script extension paths with the key being the namespace of a mod
  60. var saved_extension_paths := {}
  61. var logged_messages: Dictionary:
  62. set(val):
  63. ModLoaderDeprecated.deprecated_changed("ModLoaderStore.logged_messages", "ModLoaderLog.logged_messages", "7.0.1")
  64. ModLoaderLog.logged_messages = val
  65. get:
  66. ModLoaderDeprecated.deprecated_changed("ModLoaderStore.logged_messages", "ModLoaderLog.logged_messages", "7.0.1")
  67. return ModLoaderLog.logged_messages
  68. # Active user profile
  69. var current_user_profile: ModUserProfile
  70. # List of user profiles loaded from user://mod_user_profiles.json
  71. var user_profiles := {}
  72. # ModLoader cache is stored in user://mod_loader_cache.json
  73. var cache := {}
  74. # Various options, which can be changed either via
  75. # Godot's GUI (with the options.tres resource file), or via CLI args.
  76. # Usage: `ModLoaderStore.ml_options.KEY`
  77. # See: res://addons/mod_loader/options/options.tres
  78. # See: res://addons/mod_loader/resources/options_profile.gd
  79. var ml_options: ModLoaderOptionsProfile
  80. var has_feature := {
  81. "editor" = OS.has_feature("editor")
  82. }
  83. # Methods
  84. # =============================================================================
  85. func _init():
  86. _update_ml_options_from_options_resource()
  87. _update_ml_options_from_cli_args()
  88. _configure_logger()
  89. # ModLoaderStore is passed as argument so the cache data can be loaded on _init()
  90. _ModLoaderCache.init_cache(self)
  91. func _exit_tree() -> void:
  92. # Save the cache to the cache file.
  93. _ModLoaderCache.save_to_file()
  94. # Update ModLoader's options, via the custom options resource
  95. #
  96. # Parameters:
  97. # - ml_options_path: Path to the options resource. See: res://addons/mod_loader/resources/options_current.gd
  98. func _update_ml_options_from_options_resource(ml_options_path := "res://addons/mod_loader/options/options.tres") -> void:
  99. # Get user options for ModLoader
  100. if not _ModLoaderFile.file_exists(ml_options_path) and not ResourceLoader.exists(ml_options_path):
  101. ModLoaderLog.fatal(str("A critical file is missing: ", ml_options_path), LOG_NAME)
  102. var options_resource: ModLoaderCurrentOptions = load(ml_options_path)
  103. if options_resource.current_options == null:
  104. ModLoaderLog.warning(str(
  105. "No current options are set. Falling back to defaults. ",
  106. "Edit your options at %s. " % ml_options_path
  107. ), LOG_NAME)
  108. else:
  109. var current_options = options_resource.current_options
  110. if not current_options is ModLoaderOptionsProfile:
  111. ModLoaderLog.error(str(
  112. "Current options is not a valid Resource of type ModLoaderOptionsProfile. ",
  113. "Please edit your options at %s. " % ml_options_path
  114. ), LOG_NAME)
  115. # Update from the options in the resource
  116. ml_options = current_options
  117. # Get options overrides by feature tags
  118. # An override is saved as Dictionary[String: ModLoaderOptionsProfile]
  119. for feature_tag in options_resource.feature_override_options.keys():
  120. if not feature_tag is String:
  121. ModLoaderLog.error(str(
  122. "Options override keys are required to be of type String. Failing key: \"%s.\" " % feature_tag,
  123. "Please edit your options at %s. " % ml_options_path,
  124. "Consult the documentation for all available feature tags: ",
  125. "https://docs.godotengine.org/en/3.5/tutorials/export/feature_tags.html"
  126. ), LOG_NAME)
  127. continue
  128. if not OS.has_feature(feature_tag):
  129. ModLoaderLog.info("Options override feature tag \"%s\". does not apply, skipping." % feature_tag, LOG_NAME)
  130. continue
  131. ModLoaderLog.info("Applying options override with feature tag \"%s\"." % feature_tag, LOG_NAME)
  132. var override_options = options_resource.feature_override_options[feature_tag]
  133. if not override_options is ModLoaderOptionsProfile:
  134. ModLoaderLog.error(str(
  135. "Options override is not a valid Resource of type ModLoaderOptionsProfile. ",
  136. "Options override key with invalid resource: \"%s\". " % feature_tag,
  137. "Please edit your options at %s. " % ml_options_path
  138. ), LOG_NAME)
  139. continue
  140. # Update from the options in the resource
  141. ml_options = override_options
  142. if not ml_options.customize_script_path.is_empty():
  143. ml_options.customize_script_instance = load(ml_options.customize_script_path).new(ml_options)
  144. # Update ModLoader's options, via CLI args
  145. func _update_ml_options_from_cli_args() -> void:
  146. # Disable mods
  147. if _ModLoaderCLI.is_running_with_command_line_arg("--disable-mods"):
  148. ml_options.enable_mods = false
  149. # Override paths to mods
  150. # Set via: --mods-path
  151. # Example: --mods-path="C://path/mods"
  152. var cmd_line_mod_path := _ModLoaderCLI.get_cmd_line_arg_value("--mods-path")
  153. if cmd_line_mod_path:
  154. ml_options.override_path_to_mods = cmd_line_mod_path
  155. ModLoaderLog.info("The path mods are loaded from has been changed via the CLI arg `--mods-path`, to: " + cmd_line_mod_path, LOG_NAME)
  156. # Override paths to configs
  157. # Set via: --configs-path
  158. # Example: --configs-path="C://path/configs"
  159. var cmd_line_configs_path := _ModLoaderCLI.get_cmd_line_arg_value("--configs-path")
  160. if cmd_line_configs_path:
  161. ml_options.override_path_to_configs = cmd_line_configs_path
  162. ModLoaderLog.info("The path configs are loaded from has been changed via the CLI arg `--configs-path`, to: " + cmd_line_configs_path, LOG_NAME)
  163. # Log level verbosity
  164. if _ModLoaderCLI.is_running_with_command_line_arg("-vvv") or _ModLoaderCLI.is_running_with_command_line_arg("--log-debug"):
  165. ml_options.log_level = ModLoaderLog.VERBOSITY_LEVEL.DEBUG
  166. elif _ModLoaderCLI.is_running_with_command_line_arg("-vv") or _ModLoaderCLI.is_running_with_command_line_arg("--log-info"):
  167. ml_options.log_level = ModLoaderLog.VERBOSITY_LEVEL.INFO
  168. elif _ModLoaderCLI.is_running_with_command_line_arg("-v") or _ModLoaderCLI.is_running_with_command_line_arg("--log-warning"):
  169. ml_options.log_level = ModLoaderLog.VERBOSITY_LEVEL.WARNING
  170. # Ignored mod_names in log
  171. var ignore_mod_names := _ModLoaderCLI.get_cmd_line_arg_value("--log-ignore")
  172. if not ignore_mod_names == "":
  173. ml_options.ignored_mod_names_in_log = ignore_mod_names.split(",")
  174. # Update static variables from the options
  175. func _configure_logger() -> void:
  176. ModLoaderLog.verbosity = ml_options.log_level
  177. ModLoaderLog.ignored_mods = ml_options.ignored_mod_names_in_log
  178. ModLoaderLog.hint_color = ml_options.hint_color