Skip to content

feat: ✨ Advanced Mod Configs #237

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

KANAjetzt
Copy link
Member

@KANAjetzt KANAjetzt commented Apr 25, 2023

Advanced Mod Configs - ModLoaderConfig and ModConfig

TLDR

This PR introduces a comprehensive rework of the Mod Config setup, addressing the requirements outlined in issue #178. Key changes include the addition of JSONSchema Validation and the implementation of a new Resource Class called ModConfig, which defines the structure of the Config Data. The existing ModLoaderConfig has been completely rewritten, offering new API methods that cater to the various needs of creating a UI for editing Mod Configs and integrating them within a Mod Project.

Example Config Editor UI Mod Download

Example Config Editor UI Mod Repo

PR for User Profile UI Update

JSON-Shema-validator Fork

New API Methods ModLoaderConfig

  • create_config(mod_id: String, config_name: String, config_data: Dictionary) -> ModConfig
    Creates a new configuration for a mod, stores it in the mod's ModData and saves it to a new config JSON file in the mod's config directory.

  • update_config(config: ModConfig) -> ModConfig
    Updates an existing ModConfig and saves it to disk.

  • delete_config(config: ModConfig) -> bool
    Deletes a ModConfig, removes the Object from the mod's ModData and deletes the corresponding JSON file.

  • set_current_config(config: ModConfig) -> void
    Sets the current configuration of a mod to the specified configuration.

  • get_config_schema(mod_id: String) -> Dictionary
    Returns the schema for the specified mod id.
    If no configuration file exists for the mod, an empty dictionary is returned.

  • get_schema_for_prop(config: ModConfig, prop: String) -> Dictionary
    Retrieves the schema for a specific property key.
    The property key is specified as a string using the . notation:
    "parentProp.childProp.nthChildProp" or "propKey"

  • _traverse_schema(schema_prop: Dictionary, prop_key_array: Array) -> Dictionary
    Recursively traverses the schema dictionary based on the provided prop_key_array and returns the corresponding schema for the target property.
    Used internally by get_schema_for_prop()

  • get_mods_with_config() -> Array
    Retrieves an Array of mods that have configuration files.

  • get_configs(mod_id: String) -> Dictionary
    Retrieves the configurations dictionary for a given mod ID.

  • get_config(mod_id: String, config_name: String) -> ModConfig
    Retrieves the configuration for a specific mod and configuration name.

  • get_default_config(mod_id: String) -> ModConfig
    Retrieves the default configuration for a specified mod ID.

  • get_current_config(mod_id: String) -> ModConfig
    Retrieves the currently active configuration for a specific mod.

  • get_current_config_name(mod_id: String) -> String
    Retrieves the name of the current configuration for a specific mod.

New Resource ModConfig

Properties

  • name: String
    Name of the config - must be unique

  • mod_id: String
    The mod_id this config belongs to

  • schema: Dictionary
    The JSON-Schema this config uses for validation

  • data: Dictionary
    The data this config holds

  • save_path: String
    The path where the JSON file for this config is stored

  • is_valid := false
    False if any data is invalid

Methods

  • get_data_as_string() -> String
    Returns the config data as a JSON String

  • get_schema_as_string() -> String
    Returns the schema as a JSON String

  • validate() -> String
    Performs the JSON-Schema validation and returns an empty string if the data is valid, or an error message if there are validation errors.

  • is_valid() -> bool
    Runs the JSON-Schema validation and returns true if valid.

  • save_to_disc() -> bool
    Saves the config data to the save_path.

  • remove_from_disc() -> bool
    Removes the config data from the save_path.

New Internal Class _ModLoaderCache

Consts

  • CACHE_FILE_PATH
    Path to the cache file - by the time of writing this is "user://ModLoaderCache.json"

Methods

  • init_cache(_ModLoaderStore) -> void
    Creates a new cache file or loads the existing one.
    ModLoaderStore is passed as parameter so the cache data can be loaded on ModLoaderStore._init()

  • add_data(key: String, data: Dictionary) -> void
    Adds data to the cache

  • get_data(key: String) -> Dictionary
    Get data from a specific key

  • get_cache() -> Dictionary
    Get the entire cache dictionary

  • has_key(key: String) -> bool
    Check if the key exists in the cache data

  • update_data(key: String, data: Dictionary) -> Dictionary
    Updates or adds data to the cache

  • remove_data(key: String) -> void
    Remove data from the cache

  • save_to_file() -> void
    Save the cache to the cache file

  • _load_file(_ModLoaderStore = ModLoaderStore) -> void
    Load the cache file data and store it in ModLoaderStore

  • _init_cache_file() -> void
    Create an empty cache file

Changes to ModLoader

New Signal

  • current_config_changed(config)
    Emitted when the current config of a mod has changed.
    This signal can be used by mod authors to apply config changes to their mod.
    Maybe there is a way for a mod to receive a signal specific to its config?
    This way, a mod would only be notified if its own current_config has changed.

Changes and Additions to ModLoaderUserProfile

The mod_list is now a Dictionary of Dictionaries.
The current_config for each mod is stored in the User Profile mod_list.

New Methods

  • set_mod_current_config(mod_id: String, config_name: String, profile_name := ModLoaderStore.current_user_profile) -> bool
    Sets the current config for a mod in a user profiles mod_list.

  • _update_mod_list(mod_list: Dictionary, mod_data := ModLoaderStore.mod_data) -> Dictionary
    Updates the mod list by checking the validity of each mod entry and making necessary modifications.

  • _generate_mod_list() -> Dictionary
    Generates a dictionary with data to be stored for each mod.

  • _generate_mod_list_entry(mod_id: String, is_active: bool) -> Dictionary
    Generates a mod list entry dictionary with the given mod ID and active status.
    If the mod has a config schema, sets the 'current_config' key to the current_config stored in the Mods ModData.

Changes to ModData

New Properties

  • configs := {}
    Stores all ModConfig Objects

  • current_config: ModConfig setget _set_current_config
    Stores the currently active ModConfig

New Methods

  • load_configs() -> void
    Load each mod config json from the mods config directory.

  • _load_config(config_file_path: String) -> void
    Creates a new ModConfig instance for each Config JSON and add it to the configs dictionary.

  • _set_current_config(new_current_config: ModConfig) -> void
    Seter function for current_config to update the User Profile

Changes to ModManifest

Properties

  • The manifest stores now the config_schema instead of config_defaults

Methods

  • load_mod_config_defaults() -> void
    Loads the default configuration for a mod.
    By loading the default.json config file or using _generate_default_config_from_schema()

  • _generate_default_config_from_schema(property: Dictionary, current_prop := {}) -> Dictionary
    Generates the default config from the default keys inside the Config JSON-Schema.

Added Utilities

_ModLoaderFile

  • remove_file(file_path: String) -> bool
    Removes a file from the given path.

ModLoaderUtils

  • get_dict_from_dict(dict: Dictionary, key: String) -> Dictionary
    Returns an empty Dictionary if the key does not exist or is not type of Dictionary.

_ModLoaderPath

  • get_dir_paths_in_dir(src_dir_path: String) -> Array
    Returns an array of directory paths inside the src dir.

  • get_file_paths_in_dir(src_dir_path: String) -> Array
    Returns an array of file paths inside the src dir.

  • get_path_to_mod_configs_dir(mod_id: String) -> String
    Get the path to a mods config folder.
    Returns an empty string if there is no config dir for this mod_id.

  • get_path_to_mod_config_file(mod_id: String, config_name: String) -> String
    Get the path to a mods config file.
    Returns an empty string if the config file does not exist.

Known issues

  • Currently, the existing Config Implementation does not have a proper deprecation process in place.
  • If the user switches to a different profile and there are different current_configs for the mods saved, no current_config_changed(config) signal is fired. This results in the mods not updating their config after the profile is switched.
    However, this is not a major issue right now because a game restart is required anyway to apply the active state of a mod.



closes #178

@KANAjetzt KANAjetzt added the enhancement New feature or request label Apr 25, 2023
@KANAjetzt KANAjetzt added this to the v6.0.0 milestone Apr 25, 2023
@KANAjetzt KANAjetzt self-assigned this Apr 25, 2023
With it `_handle_mod_config()` | `_get_config_default_data()` generates the `config_default`s from the `config_schema` default keys | `get_config_default_data_as_string()` | `get_config_schema_as_string()` | `is_config_valid()` validates the config based on the schema using the `JSON_Schema_Validator` Addon
If there is no json file for this mod config yet
With this change, the config data of the `config_defaults` defined in the `config_schema` is available in the first run.
used in `ModManifest` to get `config_schema`
Only returning the full config data without the key parameter, as it only allowed retrieving top-level properties. | Removing the distinction between user config and default config. The default config is now generated based on the default key of the config_schema and saved to the mod config file. This ensures that there is always a user config. - To revert to the default config, simply delete the config JSON file.
remove `not` from if statement, and removed the call to the original `_load_mod_configs()` function
@KANAjetzt KANAjetzt force-pushed the feat_advanced_mod_config branch from 3d906d8 to 9661d5b Compare May 5, 2023 10:02
@ithinkandicode ithinkandicode linked an issue May 7, 2023 that may be closed by this pull request
KANAjetzt added 5 commits May 9, 2023 15:49
Reworked the config loading in *mod_manifest.gd* and *mod_data.gd* | The `current_config` for each mod is now stored in the user profiles.
Removed `mod` from functions in `config.gd` to maintain uniformity with other functions within this class. | Configurations are now stored inside a dictionary instead of an array in `ModData`, where the key represents the config name. This change makes it much easier to access and locate the appropriate config based on its name.
added `get_path_to_mod_configs()` in `_ModLoaderPath`
@KANAjetzt KANAjetzt requested review from ithinkandicode, Qubus0, otDan and a team May 24, 2023 14:24
Copy link
Collaborator

@Qubus0 Qubus0 left a comment

Choose a reason for hiding this comment

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

Exquisite job on this one. Thank you!
Only a few smaller things to remark, overall approved. I'll let the others have a look at it as well though

@KANAjetzt KANAjetzt force-pushed the feat_advanced_mod_config branch from 3fa2529 to 274fcd6 Compare May 31, 2023 22:28
KANAjetzt added 6 commits June 1, 2023 00:30
`load_mod_config_defaults()` is now called in `load_configs()` in `mod_data.gd` to allow skipping the config loading if the default values in the schema are invalid. Additionally, the `default.json` file is now validated, so the default config is regenerated if the schema has changed and the defaults are no longer valid.
Implemented `_ModLoaderCache` to store the MD5s of the config schema in `"user://ModLoaderCache.json"`. This enables checking if the config schema has changed since the last start, allowing the regeneration of the default config only if a change is detected.
to align with the other methods
@Qubus0 Qubus0 mentioned this pull request Jun 3, 2023
Copy link
Collaborator

@Qubus0 Qubus0 left a comment

Choose a reason for hiding this comment

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

Very nice.
Looks like the others won't really get around to add their review, so take this approval!

KANAjetzt added 2 commits June 3, 2023 23:16
Fixed an error that occurred when the `default.json` config file existed, but the cache had been deleted.
@KANAjetzt KANAjetzt added this pull request to the merge queue Jun 3, 2023
Merged via the queue into GodotModding:development with commit a147471 Jun 3, 2023
@KANAjetzt KANAjetzt deleted the feat_advanced_mod_config branch June 3, 2023 21:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Expand configs: Add type, min/max
2 participants