# CORE FRAMEWORK SOURCE FILE # Copyright (c) 2024 The StarOpenSource Project & Contributors # Licensed under the GNU Affero General Public License v3 # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . ## Initializes and manages the framework. ## ## The [b]CORE Object[/b] is responsible for initializing, managing and ## serving the CORE Framework to the developer. extends Node class_name Core # Constants ## The release number const version_release: int = 1 ## The release type const version_type: CoreTypes.VersionType = CoreTypes.VersionType.BETA ## The release type number. Resets on every new release and release type. const version_typerelease: int = 2 # Modules ## Use this to access CORE's logging implementation. var logger: CoreBaseModule ## Use this to access various useful functions. var misc: CoreBaseModule ## Use this to access the scene management system. var sms: CoreBaseModule ## Use this to access the graphical log. Serves no importance to you (probably). var logui: CoreBaseModule ## Use this to access CORE's builtin HTTP request maker. var edl: CoreBaseModule ## Use this to access configuration and settings files easily. var storage: CoreBaseModule # Variables ## Contains CORE's load path var basepath: String ## Holds the configuration[br] ## [br] ## [b]NEVER access this yourself. To change the configuration file, use [method Core.reload_configuration] instead.[/b] var config: CoreConfiguration ## Contains all loaded custom modules. var custom_modules: Dictionary = {} ## Contains the custom modules node. var custom_modules_node: Node # Preinitialization func _init(new_config: CoreConfiguration = CoreConfiguration.new()) -> void: name = "CORE" if !check_godot_version(): return if !determine_basepath(): queue_free() custom_modules_node = Node.new() reload_configuration(new_config) initialize_modules() apply_configuration() # Initialization func _ready() -> void: inject_modules() custom_modules_node.name = "Custom Modules" add_child(custom_modules_node) logger.infof("Core", "Initialized CORE successfully") # Initialize modules ## Initializes all modules during the first initialization phase.[br] ## [br] ## [b]NEVER call this yourself! You will break everything and risk a crash![/b] func initialize_modules() -> void: # Create Nodes logger = CoreBaseModule.new() misc = CoreBaseModule.new() sms = CoreBaseModule.new() logui = CoreBaseModule.new() edl = CoreBaseModule.new() storage = CoreBaseModule.new() # Set names logger.name = "Logger" misc.name = "Misc" sms.name = "SceneManagementSystem" logui.name = "LogUI" edl.name = "EasyDownLoader" storage.name = "Storage" # Set scripts logger.set_script(ResourceLoader.load(basepath + "src/Logger.gd")) misc.set_script(ResourceLoader.load(basepath + "src/Misc.gd")) sms.set_script(ResourceLoader.load(basepath + "src/Sms.gd")) logui.set_script(ResourceLoader.load(basepath + "src/Logui.gd")) edl.set_script(ResourceLoader.load(basepath + "src/Edl.gd")) storage.set_script(ResourceLoader.load(basepath + "src/Storage.gd")) # Set reference to self logger.core = self misc.core = self sms.core = self logui.core = self edl.core = self storage.core = self # Call _initialize() (workaround as modules cannot access "core" during _init()) logger._initialize() misc._initialize() sms._initialize() logui._initialize() edl._initialize() storage._initialize() # Inject modules into the SceneTree ## Injects CORE's builtin modules into the SceneTree.[br] ## [br] ## [b]NEVER call this yourself! You will break everything and risk a crash![/b] func inject_modules() -> void: add_child(logger) add_child(misc) add_child(sms) add_child(logui) add_child(edl) add_child(storage) # Registers a custom module ## Registers a new custom module. func register_custom_module(module_name: String, module_class: CoreBaseModule) -> bool: logger.verbf("Core", "Registering new custom module \"" + module_name + "\"") if !config.custom_modules: logger.errorf("Core", "Registering module failed: Custom module support is disabled.") return false if custom_modules.has(module_name): logger.errorf("Core", "Registering module failed: A custom module with the name \"" + module_name + "\" already exists.") return false module_class.name = module_name logger.diagf("Core", "Adding module to SceneTree") custom_modules_node.add_child(module_class) logger.diagf("Core", "Merging module with custom_modules") custom_modules.merge({ module_name: module_class }) logger.diagf("Core", "Initializing custom module") module_class._initialize() logger.diagf("Core", "Updating custom module configuration") module_class._pull_config() return true # Unregisters a custom module ## Unregisters a custom module, making it no longer function. func unregister_custom_module(module_name: String) -> void: logger.verbf("Core", "Unregistering custom module \"" + module_name + "\"") if !custom_modules.has(module_name): logger.errorf("Core", "Unregistering module failed: A custom module with the name \"" + module_name + "\" does not exist.") return custom_modules_node.remove_child(get_custom_module(module_name)) custom_modules.erase(module_name) # Returns a custom module ## Returns a loaded custom module for access. func get_custom_module(module_name: String) -> CoreBaseModule: logger.diagf("Core", "Getting custom module \"" + module_name + "\"") if !custom_modules.has(module_name): logger.errorf("Core", "Getting module failed: A custom module with the name \"" + module_name + "\" does not exist.") return return custom_modules[module_name] # (Re-)Load configuration ## Loads a (new) configuration file and applies it to all modules. func reload_configuration(new_config: CoreConfiguration = CoreConfiguration.new()) -> void: var initialized = config != null if initialized: logger.verbf("Core", "Reloading CORE's configuration") config = new_config if is_devmode(): # Override configuration in development mode config.logger_level = CoreTypes.LoggerLevel.VERB if initialized: logger.verbf("Core", "Overrode configuration (development mode)") if initialized: apply_configuration() # Call _pull_config() functions ## Applies the newly applied configuration.[br] ## [br] ## [b]NEVER call this yourself unless you know what you are doing![/b] func apply_configuration() -> void: logger.verbf("Core", "Applying configuration") if is_devmode(): logger.warnf("Core", "The CORE Framework is in development mode. Here be dragons!") if config.headless: logger.warnf("Core", "CORE is in headless mode. Certain modules will not work as expected.") edl._pull_config() if !config.custom_modules: logger.verbf("Core", "Removing all custom modules (custom modules support is disabled)") for module in custom_modules: unregister_custom_module(module) logger._pull_config() misc._pull_config() sms._pull_config() logui._pull_config() if config.custom_modules: for module in custom_modules: logger.diagf("Core", "Updating configuration for custom module \"" + module.name + "\"") module._pull_config() # Return development mode status ## Returns if the CORE Framework is in development mode. func is_devmode() -> bool: return config.debugging and basepath == "res://" and OS.is_debug_build() # Replaces variables with human-friendly strings ## Replaces placeholders with human-friendly strings You can use the following placeholders:[br] ## - [code]%release%[/code]: Returns the release number.[br] ## - [code]%release_type%[/code]: Returns the typerelease number[br] ## - [code]%release_semantic%[/code]: Returns the result of [method Core.get_version_semantic], example [i]5.2.3[/i][br] ## - [code]%type%[/code]: Returns the release type as a word, for example [i]Release Candidate[/i][br] ## - [code]%type_technical%[/code]: Returns the release type as one or two lowercase letters, for example [i]rc[/i][br] ## - [code]%devmode%[/code]: Returns the development mode status[br] ## - [code]%headless%[/code]: Returns the headless mode status func get_formatted_string(string: String) -> String: # Version strings string = string.replace("%release%", str(version_release)) string = string.replace("%release_type%", str(version_typerelease)) var semantic_version: Array[int] = get_version_semantic() string = string.replace("%release_semantic%", str(semantic_version[0]) + "." + str(semantic_version[1]) + "." + str(semantic_version[2])) match(version_type): CoreTypes.VersionType.RELEASE: string = string.replace("%type%", "Release") string = string.replace("%type_technical%", "r") CoreTypes.VersionType.RELEASECANDIDATE: string = string.replace("%type%", "Release Candidate") string = string.replace("%type_technical%", "rc") CoreTypes.VersionType.BETA: string = string.replace("%type%", "Beta") string = string.replace("%type_technical%", "b") CoreTypes.VersionType.ALPHA: string = string.replace("%type%", "Alpha") string = string.replace("%type_technical%", "a") _: await logger.crashf("Core", "Invalid version type " + str(version_type), true) # Development mode if is_devmode(): string = string.replace("%devmode%", "Enabled") else: string = string.replace("%devmode%", "Disabled") # Headless mode if config.headless: string = string.replace("%headless%", "Enabled") else: string = string.replace("%headless%", "Disabled") # Custom module support if config.custom_modules: string = string.replace("%custommodules%", "Enabled") else: string = string.replace("%custommodules%", "Disabled") return string # Return CORE's version in the semantic versioning scheme ## Returns CORE's versioning scheme into the semantic versioning scheme.[br] ## The first integer contains the release number, the second integer contains the release type ([code]0[/code] for alpha, [code]1[/code] for beta, [code]2[/code] for rc and [code]3[/code] for release and the last integer contains the typerelease number. func get_version_semantic() -> Array[int]: var version_type_int: int match(version_type): CoreTypes.VersionType.RELEASE: version_type_int = 3 CoreTypes.VersionType.RELEASECANDIDATE: version_type_int = 2 CoreTypes.VersionType.BETA: version_type_int = 1 CoreTypes.VersionType.ALPHA: version_type_int = 0 return [version_release, version_type_int, version_typerelease] # Determines CORE's installation/base path ## Determines CORE's installation/base path[br] ## [br] ## [b]Calling this function is likely to be safe, but shouldn't be done nonetheless![/b] func determine_basepath() -> bool: if FileAccess.file_exists("res://.corebasepath"): basepath = "res://" elif FileAccess.file_exists("res://CORE/.corebasepath"): basepath = "res://CORE/" elif FileAccess.file_exists("res://addons/CORE/.corebasepath"): basepath = "res://addons/CORE/" else: assert(false, "CORE is not located at 'res://CORE/', aborting initialization") return false return true # Checks Godot's version ## Checks compatibility with the running version. func check_godot_version() -> bool: var version: Dictionary = Engine.get_version_info() match(version["major"]): 4: pass _: printerr("The CORE Framework does not support Godot versions older or newer than 4.x.x") return false match(version["minor"]): 0: printerr("The CORE Framework does not support Godot versions older than 4.2.x. Please update to Godot 4.2.x to ensure full compatibility.") 1: printerr("The CORE Framework does not support Godot versions older than 4.2.x. Please update to Godot 4.2.x to ensure full compatibility.") 2: pass _: printerr("The CORE Framework does not support Godot versions newer than 4.2.x. Please downgrade to Godot 4.2.x.") return false if version["status"] != "stable": printerr("The CORE Framework does not support unstable Godot versions. Please switch to Godot stable 4.2.x.") return false return true