Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions addons/mod_loader/api/config.gd
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ static func create_config(mod_id: String, config_name: String, config_data: Dict
)

# Check if the mod_config is valid
if not mod_config.is_valid:
#if not mod_config.is_valid:
if not mod_config.is_valid():
return null

# Store the mod_config in the mod's ModData
Expand Down Expand Up @@ -86,7 +87,8 @@ static func update_config(config: ModConfig) -> ModConfig:
return null

# Check if the config passed validation
if not config.is_valid:
#if not config.is_valid:
if not config.is_valid():
ModLoaderLog.error("Update for config \"%s\" failed validation with error message \"%s\"" % [config.name, error_message], LOG_NAME)
return null

Expand Down
173 changes: 117 additions & 56 deletions addons/mod_loader/api/log.gd
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,15 @@ enum VERBOSITY_LEVEL {
DEBUG, ## For debugging, can get quite verbose
}

## Keeps track of logged messages, to avoid flooding the log with duplicate notices
## Can also be used by mods, eg. to create an in-game developer console that
## shows messages
static var logged_messages := {
"all": {},
"by_mod": {},
"by_type": {
"fatal-error": {},
"error": {},
"warning": {},
"info": {},
"success": {},
"debug": {},
"hint": {},
}
enum VERBOSITY_COLOR {
ERROR, ## For errors and fatal errors
WARNING, ## For warnings
INFO, ## For everything informational
SUCCESS,
DEBUG, ## For debugging, can get quite verbose
hint,
}

## Verbosity/Logging level.
## Used to filter out messages below the set level
## (if the [enum VERBOSITY_LEVEL] int of a new entry is larger than the [member verbosity] it is ignored)
static var verbosity: VERBOSITY_LEVEL = VERBOSITY_LEVEL.DEBUG

## Array of mods that should be ignored when logging messages (contains mod IDs as strings)
static var ignored_mods: Array[String] = []

## Highlighting color for hint type log messages
static var hint_color := Color("#70bafa")

## This Sub-Class represents a log entry in ModLoader.
class ModLoaderLogEntry:
extends Resource
Expand Down Expand Up @@ -99,9 +80,15 @@ class ModLoaderLogEntry:

## Get the prefix string for the log entry, including the log type and mod name.[br]
## [br]
## [b]Parameters:[/b][br]
## [param exclude_type] ([bool]): (Optional) If true, the log type (e.g., DEBUG, WARN) will be excluded from the prefix. Default is false.[br]
## [br]
## [b]Returns:[/b] [String]
func get_prefix() -> String:
return "%s %s: " % [type.to_upper(), mod_name]
func get_prefix(exclude_type := false) -> String:
return "%s%s: " % [
"" if exclude_type else "%s " % type.to_upper(),
mod_name
]


## Generate an MD5 hash of the log entry (prefix + message).[br]
Expand Down Expand Up @@ -323,8 +310,8 @@ static func get_all() -> Array:
var log_entries := []

# Get all log entries
for entry_key in logged_messages.all.keys():
var entry: ModLoaderLogEntry = logged_messages.all[entry_key]
for entry_key in ModLoaderStore.logged_messages.all.keys():
var entry: ModLoaderLogEntry = ModLoaderStore.logged_messages.all[entry_key]
log_entries.append_array(entry.get_all_entries())

# Sort them by time
Expand All @@ -343,12 +330,12 @@ static func get_all() -> Array:
static func get_by_mod(mod_name: String) -> Array:
var log_entries := []

if not logged_messages.by_mod.has(mod_name):
if not ModLoaderStore.logged_messages.by_mod.has(mod_name):
error("\"%s\" not found in logged messages." % mod_name, _LOG_NAME)
return []

for entry_key in logged_messages.by_mod[mod_name].keys():
var entry: ModLoaderLogEntry = logged_messages.by_mod[mod_name][entry_key]
for entry_key in ModLoaderStore.logged_messages.by_mod[mod_name].keys():
var entry: ModLoaderLogEntry = ModLoaderStore.logged_messages.by_mod[mod_name][entry_key]
log_entries.append_array(entry.get_all_entries())

return log_entries
Expand All @@ -364,8 +351,8 @@ static func get_by_mod(mod_name: String) -> Array:
static func get_by_type(type: String) -> Array:
var log_entries := []

for entry_key in logged_messages.by_type[type].keys():
var entry: ModLoaderLogEntry = logged_messages.by_type[type][entry_key]
for entry_key in ModLoaderStore.logged_messages.by_type[type].keys():
var entry: ModLoaderLogEntry = ModLoaderStore.logged_messages.by_type[type][entry_key]
log_entries.append_array(entry.get_all_entries())

return log_entries
Expand All @@ -391,6 +378,18 @@ static func get_all_entries_as_string(log_entries: Array) -> Array:
# Internal log functions
# =============================================================================

static func _print_rich(prefix: String, message: String, color: Color, bold := true) -> void:
var in_editor: bool = OS.has_feature("editor") if not ModLoaderStore else ModLoaderStore.has_feature.editor
if in_editor:
var prefix_text := "[b]%s[/b]" % prefix if bold else prefix
print_rich("[color=%s]%s[/color]%s" % [
color.to_html(false),
prefix_text,
message
])
else:
print(prefix + message)

static func _log(message: String, mod_name: String, log_type: String = "info", only_once := false) -> void:
if _is_mod_name_ignored(mod_name):
return
Expand Down Expand Up @@ -422,63 +421,125 @@ static func _log(message: String, mod_name: String, log_type: String = "info", o
_write_to_log_file(JSON.stringify(get_stack(), " "))
assert(false, message)
"error":
printerr(log_entry.get_prefix() + message)
if ModLoaderStore.has_feature.editor:
printerr(log_entry.get_prefix(true) + message)
else:
printerr(log_entry.get_prefix() + message)
push_error(message)
_write_to_log_file(log_entry.get_entry())
"warning":
if verbosity >= VERBOSITY_LEVEL.WARNING:
print(log_entry.get_prefix() + message)
if _get_verbosity() >= VERBOSITY_LEVEL.WARNING:
_print_rich(
log_entry.get_prefix(),
message,
_get_color(VERBOSITY_COLOR.WARNING)
)
push_warning(message)
_write_to_log_file(log_entry.get_entry())
"info", "success":
if verbosity >= VERBOSITY_LEVEL.INFO:
print(log_entry.get_prefix() + message)
"success":
if _get_verbosity() >= VERBOSITY_LEVEL.INFO:
_print_rich(
log_entry.get_prefix(),
message,
_get_color(VERBOSITY_COLOR.SUCCESS)
)
_write_to_log_file(log_entry.get_entry())
"info":
if _get_verbosity() >= VERBOSITY_LEVEL.INFO:
_print_rich(
log_entry.get_prefix(),
message,
_get_color(VERBOSITY_COLOR.INFO)
)
_write_to_log_file(log_entry.get_entry())
"debug":
if verbosity >= VERBOSITY_LEVEL.DEBUG:
print(log_entry.get_prefix() + message)
if _get_verbosity() >= VERBOSITY_LEVEL.DEBUG:
_print_rich(
log_entry.get_prefix(),
message,
_get_color(VERBOSITY_COLOR.DEBUG),
true if not ModLoaderStore else ModLoaderStore.ml_options.debug_bold
)
_write_to_log_file(log_entry.get_entry())
"hint":
if OS.has_feature("editor") and verbosity >= VERBOSITY_LEVEL.DEBUG:
print_rich("[color=%s]%s[/color]" % [hint_color.to_html(false), log_entry.get_prefix() + message])
if ModLoaderStore.has_feature.editor:
if _get_verbosity() >= VERBOSITY_LEVEL.DEBUG:
_print_rich(
log_entry.get_prefix(),
message,
_get_color(VERBOSITY_COLOR.hint)
)


static func _is_mod_name_ignored(mod_log_name: String) -> bool:
if not ModLoaderStore:
return false

var ignored_mod_log_names := ModLoaderStore.ml_options.ignored_mod_names_in_log as Array

static func _is_mod_name_ignored(mod_name: String) -> bool:
if ignored_mods.is_empty():
# No ignored mod names
if ignored_mod_log_names.size() == 0:
return false

if mod_name in ignored_mods:
# Directly match a full mod log name. ex: "ModLoader:Deprecated"
if mod_log_name in ignored_mod_log_names:
return true

# Match a mod log name with a wildcard. ex: "ModLoader:*"
for ignored_mod_name in ignored_mod_log_names:
if ignored_mod_name.ends_with("*"):
if mod_log_name.begins_with(ignored_mod_name.trim_suffix("*")):
return true

# No match
return false

static func _get_color(verbosity: VERBOSITY_COLOR) -> Color:
if not ModLoaderStore:
return Color("#d4d4d4")

var color = ModLoaderStore.ml_options.get(
"%s_color" % VERBOSITY_COLOR.keys()[verbosity].to_lower()
)
if color == null:
return Color("#d4d4d4")

return color

static func _get_verbosity() -> int:
if not ModLoaderStore:
return VERBOSITY_LEVEL.DEBUG
return ModLoaderStore.ml_options.log_level

static func _store_log(log_entry: ModLoaderLogEntry) -> void:
# HACK: this makes logs from ModLoaderStore unable to be stored
if not ModLoaderStore:
return
var existing_entry: ModLoaderLogEntry

# Store in all
# If it's a new entry
if not logged_messages.all.has(log_entry.get_md5()):
logged_messages.all[log_entry.get_md5()] = log_entry
if not ModLoaderStore.logged_messages.all.has(log_entry.get_md5()):
ModLoaderStore.logged_messages.all[log_entry.get_md5()] = log_entry
# If it's a existing entry
else:
existing_entry = logged_messages.all[log_entry.get_md5()]
existing_entry = ModLoaderStore.logged_messages.all[log_entry.get_md5()]
existing_entry.time = log_entry.time
existing_entry.stack.push_back(log_entry)

# Store in by_mod
# If the mod is not yet in "by_mod" init the entry
if not logged_messages.by_mod.has(log_entry.mod_name):
logged_messages.by_mod[log_entry.mod_name] = {}
if not ModLoaderStore.logged_messages.by_mod.has(log_entry.mod_name):
ModLoaderStore.logged_messages.by_mod[log_entry.mod_name] = {}

logged_messages.by_mod[log_entry.mod_name][log_entry.get_md5()] = log_entry if not existing_entry else existing_entry
ModLoaderStore.logged_messages.by_mod[log_entry.mod_name][log_entry.get_md5()] = log_entry if not existing_entry else existing_entry

# Store in by_type
logged_messages.by_type[log_entry.type.to_lower()][log_entry.get_md5()] = log_entry if not existing_entry else existing_entry
ModLoaderStore.logged_messages.by_type[log_entry.type.to_lower()][log_entry.get_md5()] = log_entry if not existing_entry else existing_entry


static func _is_logged_before(entry: ModLoaderLogEntry) -> bool:
if not logged_messages.all.has(entry.get_md5()):
if not ModLoaderStore.logged_messages.all.has(entry.get_md5()):
return false

return true
Expand Down
3 changes: 3 additions & 0 deletions addons/mod_loader/api/mod.gd
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ const LOG_NAME := "ModLoader:Mod"
static func install_script_extension(child_script_path: String) -> void:
var mod_id: String = _ModLoaderPath.get_mod_dir(child_script_path)
var mod_data: ModData = get_mod_data(mod_id)
if mod_data == null:
ModLoaderLog.warning('"%s" is not a valid mod id! Please ensure the supplied path is valid!' % mod_id, LOG_NAME)

if not ModLoaderStore.saved_extension_paths.has(mod_data.manifest.get_mod_id()):
ModLoaderStore.saved_extension_paths[mod_data.manifest.get_mod_id()] = []
ModLoaderStore.saved_extension_paths[mod_data.manifest.get_mod_id()].append(child_script_path)
Expand Down
2 changes: 1 addition & 1 deletion addons/mod_loader/api/profile.gd
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ static func _generate_mod_list_entry(mod_id: String, is_active: bool) -> Diction
# Set the current_config if the mod has a config schema and is active
if is_active and not ModLoaderConfig.get_config_schema(mod_id).is_empty():
var current_config: ModConfig = ModLoaderStore.mod_data[mod_id].current_config
if current_config and current_config.is_valid:
if current_config and current_config.is_valid():
# Set to the current_config name if valid
mod_list_entry.current_config = current_config.name
else:
Expand Down
1 change: 1 addition & 0 deletions addons/mod_loader/internal/dependency.gd
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ static func check_dependencies(mod: ModData, is_required := true, dependency_cha
_handle_missing_dependency(mod_id, dependency_id)
# Flag the mod so it's not loaded later
mod.is_loadable = false
mod.is_active = false
else:
var dependency: ModData = ModLoaderStore.mod_data[dependency_id]

Expand Down
3 changes: 2 additions & 1 deletion addons/mod_loader/internal/file.gd
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ static func get_json_as_dict_from_zip(zip_path: String, file_path: String, is_fu
for path in reader.get_files():
if Array(path.rsplit("/", false, 1)).back() == file_path:
full_path = path
if not full_path:
#if not full_path:
if full_path.is_empty():
ModLoaderLog.error("File was not found in zip at path %s" % [file_path], LOG_NAME)
return {}

Expand Down
11 changes: 0 additions & 11 deletions addons/mod_loader/internal/godot.gd
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ const AUTOLOAD_CONFIG_HELP_MSG := "To configure your autoloads, go to Project >
const ENGINE_VERSION_HEX_4_2_2 := 0x040202
const ENGINE_VERSION_HEX_4_2_0 := 0x040200

static var engine_version_hex: int = Engine.get_version_info().hex


# Check autoload positions:
# Ensure 1st autoload is `ModLoaderStore`, and 2nd is `ModLoader`.
static func check_autoload_positions() -> void:
Expand Down Expand Up @@ -106,11 +103,3 @@ static func get_autoload_index(autoload_name: String) -> int:
var autoload_index := autoloads.find(autoload_name)

return autoload_index


static func is_version_below(version_hex: int) -> bool:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like is_version_below is still called in file.gd

return engine_version_hex < version_hex


static func is_version_above(version_hex: int) -> bool:
return engine_version_hex > version_hex
5 changes: 1 addition & 4 deletions addons/mod_loader/internal/hooks.gd
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,10 @@ extends Object

const LOG_NAME := "ModLoader:Hooks"

static var any_mod_hooked := false


## Internal ModLoader method. [br]
## To add hooks from a mod use [method ModLoaderMod.add_hook].
static func add_hook(mod_callable: Callable, script_path: String, method_name: String) -> void:
any_mod_hooked = true
ModLoaderStore.any_mod_hooked = true
var hash := get_hook_hash(script_path, method_name)
if not ModLoaderStore.modding_hooks.has(hash):
ModLoaderStore.modding_hooks[hash] = []
Expand Down
Loading