extends Node ## The game settings and the players, shared by the whole game. ## ## Registered as the [code]Settings[/code] autoload. const MAX_NUMBER_OF_PLAYERS: int = 7 ## How many letters a search needs before it also finds names with the letters in order ## but apart, e.g. "lttp" for "A Link to the Past". Shorter searches only find names ## that contain the text, since a letter or two in order matches most of the library. const LETTERS_FOR_LOOSE_SEARCH: int = 3 ## The MusicServers that can always be picked in the settings. const SERVERS: Array[String] = [ "https://music.sanplex.xyz", "https://tmusic.sanplex.xyz", "https://rmusic.sanplex.xyz", "https://old-music.sanplex.xyz", "http://localhost:8080", ] ## The server debug mode uses. const DEBUG_SERVER: String = "http://localhost:8080" ## The winning score debug mode starts with, so a game is quick to win while testing. const DEBUG_WINNING_SCORE: int = 5 ## The settings the game saves by itself, by section, in [method saved_settings_path]. ## Count plays in statistics and Prefer less played soundtracks are left out on purpose. const SAVED_SETTINGS: Dictionary[String, Array] = { "quiz": [ &"auto_repeat_song", &"stop_after_current", &"hide_next_track", &"hide_beginning", &"hide_for_seconds", &"hide_length", &"hide_ticks", &"winning_score", &"inspiration_list_speed", ], "window": [&"fullscreen"], } ## When this environment variable is set, the settings files are kept in that folder ## instead of the user's config folder. The tests set it so they never touch the real files. const CONFIG_DIR_ENV: String = "MUSICPLAYER_CONFIG_DIR" ## The file the user edits by hand. The game writes it only when it is missing, with every ## setting commented out. const CONFIG_FILE_NAME: String = "config.ini" const CONFIG_TEMPLATE: String = """# MusicPlayer config. The game never changes this file, so edit it as you like. # Lines starting with # or ; are comments. Remove the # in front of a setting to use it, # and put it back to go back to the default. [server] # The MusicServer to use when the game starts. Another one can still be picked in # Settings for as long as the game runs. Debug mode uses http://localhost:8080. #url = https://music.sanplex.xyz # Password for getting an API token from the server. #password = [game] # true or false. Debug mode uses the local server, starts with a winning score of 5 and # shows DEBUG in the top bar. Without this setting, the game is in debug mode when run # from the Godot editor and not when exported. #debug = true """ var default_path: String = "https://music.sanplex.xyz" ## Password for getting an API token from the server. Any password works when the ## server has none set. var api_password: String = "" ## The servers that can be picked in Settings: [constant SERVERS], plus the server from ## the config file when it is not one of them. var servers: Array[String] = SERVERS.duplicate() var selected_server: int = 0 var is_local: bool = false var is_debug: bool = false var stop_after_current: bool = true var auto_repeat_song: bool = true var hide_next_track: bool = true var add_to_stats: bool = true var use_low_played_mode: bool = false var winning_score: int = 20 var fullscreen: bool = false var play_local: bool = false var inspiration_list_speed: int = 1 var hide_for_seconds: float = 5.0 var hide_beginning: bool = false var hide_length: bool = false var hide_ticks: bool = false var player_array: Array[PlayerObject] var edit_players: bool = false var currently_syncing: bool = false var character_select_open: bool = false ## The game version, set in Project Settings > Application > Config > Version. ## Releases are made from it, see RELEASING.md. var version: String = ProjectSettings.get_setting("application/config/version", "") # Autoloads are ready before any scene, so the server and debug mode from the config file # are used from the very first request. The saved settings come after, so the debug # winning score is only used until another one is saved. func _ready() -> void: apply_config(load_config()) load_saved_settings() if fullscreen: DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN) ## Uses the server, password and debug mode from the hand-edited [param config]. Settings ## left out of it get their defaults. func apply_config(config: IniFile) -> void: is_debug = config.get_bool("game", "debug", OS.has_feature("debug")) default_path = DEBUG_SERVER if is_debug else SERVERS[0] if is_debug: winning_score = DEBUG_WINNING_SCORE var url: String = config.get_string("server", "url") if !url.is_empty(): default_path = url.trim_suffix("/") if !servers.has(default_path): servers.append(default_path) selected_server = servers.find(default_path) api_password = config.get_string("server", "password") ## The folder with the settings files: a MusicPlayer folder in the user's config folder, ## e.g. [code]~/.config[/code] on Linux and [code]%APPDATA%[/code] on Windows. func config_dir() -> String: var dir: String = OS.get_environment(CONFIG_DIR_ENV) if dir.is_empty(): dir = OS.get_config_dir().path_join("MusicPlayer") return dir ## Where the hand-edited config file is. func config_path() -> String: return config_dir().path_join(CONFIG_FILE_NAME) ## Where the game saves its settings. Debug mode uses a file of its own, so testing never ## changes the real settings. func saved_settings_path() -> String: var file_name: String = "settings_debug.cfg" if is_debug else "settings.cfg" return config_dir().path_join(file_name) ## Reads the hand-edited config file. When it is missing, writes one with every setting ## commented out, so there is a file to edit. func load_config() -> IniFile: var path: String = config_path() if !FileAccess.file_exists(path): DirAccess.make_dir_recursive_absolute(path.get_base_dir()) var template: FileAccess = FileAccess.open(path, FileAccess.WRITE) if template != null: template.store_string(CONFIG_TEMPLATE) return IniFile.load_file(path) ## Loads the settings the game saved. Settings missing from the file, or saved with the ## wrong type, keep their current value. func load_saved_settings() -> void: var file: ConfigFile = ConfigFile.new() if file.load(saved_settings_path()) != OK: return for section: String in SAVED_SETTINGS: for setting: StringName in SAVED_SETTINGS[section]: set(setting, _saved_value(file, section, setting, get(setting))) ## Saves the settings in [constant SAVED_SETTINGS], so they are the same the next time ## the game starts. func save_settings() -> void: var path: String = saved_settings_path() DirAccess.make_dir_recursive_absolute(path.get_base_dir()) var file: ConfigFile = ConfigFile.new() for section: String in SAVED_SETTINGS: for setting: StringName in SAVED_SETTINGS[section]: file.set_value(section, setting, get(setting)) var error: Error = file.save(path) if error != OK: GameLog.warning("Could not save the settings to %s: %s" % [path, error_string(error)]) ## Turns fullscreen on or off and saves it. func set_fullscreen(on: bool) -> void: fullscreen = on if on: DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN) else: DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) save_settings() # The saved value of [param key], or [param default] when it is missing or of another type func _saved_value(file: ConfigFile, section: String, key: String, default: Variant) -> Variant: var value: Variant = file.get_value(section, key, default) if typeof(value) != typeof(default): push_warning("%s in [%s] should be a %s, not %s" % [key, section, type_string(typeof(default)), var_to_str(value)]) return default return value func delete_children(node: Node) -> void: for n: Node in node.get_children(): node.remove_child(n) n.queue_free()