extends Node # SettingsManager - Global singleton for handling game settings persistence # Autoloaded as "SettingsManager" const SETTINGS_FILE = "user://game_settings.cfg" const RESOLUTIONS = [ Vector2i(1024, 576), Vector2i(1280, 720), Vector2i(1366, 768), Vector2i(1600, 900), Vector2i(1920, 1080), Vector2i(2560, 1440) ] # Default values var settings = { "video": { "fullscreen": true, "vsync": true, "resolution_idx": 1, "msaa": 0, # 0: Disabled, 1: 2x, 2: 4x, 3: 8x "shadow_quality": 2, # 0: Low, 1: Med, 2: High, 3: Ultra "fps_cap": 2 # 0: Unlimited, 1: 30, 2: 60, 3: 120, 4: 144 }, "audio": { "master_volume": 0.8, "music_volume": 0.7, "sfx_volume": 0.9 }, "controls": { "use_controller": false, # Movement "move_up": KEY_W, "move_up_alt": KEY_UP, "move_down": KEY_S, "move_down_alt": KEY_DOWN, "move_left": KEY_A, "move_left_alt": KEY_LEFT, "move_right": KEY_D, "move_right_alt": KEY_RIGHT, # Actions "grab": KEY_SPACE, "grab_alt": KEY_J, "put": KEY_R, "put_alt": KEY_K, "tekton_grab": KEY_G, "tekton_grab_alt": KEY_L, # Power-Up Controls "use_powerup": KEY_F, "use_powerup_alt": KEY_SHIFT, # Power Bar Controls / Special "attack_mode": KEY_Q, "attack_mode_alt": KEY_U } } signal settings_applied signal control_remapped(action: String, key: int) func _ready(): load_settings() apply_all_settings() func load_settings(): var config = ConfigFile.new() var err = config.load(SETTINGS_FILE) if err == OK: for section in settings.keys(): for key in settings[section].keys(): settings[section][key] = config.get_value(section, key, settings[section][key]) print("[Settings] Loaded.") else: print("[Settings] Using defaults.") func save_settings(): var config = ConfigFile.new() for section in settings.keys(): for key in settings[section].keys(): config.set_value(section, key, settings[section][key]) config.save(SETTINGS_FILE) func apply_all_settings(): apply_video_settings() apply_audio_settings() apply_control_settings() emit_signal("settings_applied") func apply_video_settings(): var video = settings.video if video.fullscreen: DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN) else: DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) # Apply resolution size when windowed var res_idx = video.resolution_idx if res_idx >= 0 and res_idx < RESOLUTIONS.size(): var target_res = RESOLUTIONS[res_idx] DisplayServer.window_set_size(target_res) # Center window after resize var screen = DisplayServer.window_get_current_screen() var screen_rect = DisplayServer.screen_get_usable_rect(screen) var window_size = DisplayServer.window_get_size() DisplayServer.window_set_position(screen_rect.position + (screen_rect.size - window_size) / 2) if video.vsync: DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED) else: DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED) # Apply MSAA var viewport = get_viewport() match video.msaa: 0: viewport.msaa_3d = Viewport.MSAA_DISABLED 1: viewport.msaa_3d = Viewport.MSAA_2X 2: viewport.msaa_3d = Viewport.MSAA_4X 3: viewport.msaa_3d = Viewport.MSAA_8X # Apply FPS Cap match video.fps_cap: 0: Engine.max_fps = 0 1: Engine.max_fps = 30 2: Engine.max_fps = 60 3: Engine.max_fps = 120 4: Engine.max_fps = 144 # Apply Shadow Quality (Simplified for Forward+) match video.shadow_quality: 0: # Low RenderingServer.directional_soft_shadow_filter_set_quality(RenderingServer.SHADOW_QUALITY_SOFT_LOW) RenderingServer.positional_soft_shadow_filter_set_quality(RenderingServer.SHADOW_QUALITY_SOFT_LOW) 1: # Medium RenderingServer.directional_soft_shadow_filter_set_quality(RenderingServer.SHADOW_QUALITY_SOFT_MEDIUM) RenderingServer.positional_soft_shadow_filter_set_quality(RenderingServer.SHADOW_QUALITY_SOFT_MEDIUM) 2: # High RenderingServer.directional_soft_shadow_filter_set_quality(RenderingServer.SHADOW_QUALITY_SOFT_HIGH) RenderingServer.positional_soft_shadow_filter_set_quality(RenderingServer.SHADOW_QUALITY_SOFT_HIGH) 3: # Ultra RenderingServer.directional_soft_shadow_filter_set_quality(RenderingServer.SHADOW_QUALITY_SOFT_ULTRA) RenderingServer.positional_soft_shadow_filter_set_quality(RenderingServer.SHADOW_QUALITY_SOFT_ULTRA) func apply_audio_settings(): var audio = settings.audio set_bus_volume("Master", audio.master_volume) set_bus_volume("Music", audio.music_volume) set_bus_volume("SFX", audio.sfx_volume) func set_bus_volume(bus_name: String, volume_linear: float): var bus_idx = AudioServer.get_bus_index(bus_name) if bus_idx != -1: AudioServer.set_bus_volume_db(bus_idx, linear_to_db(volume_linear)) AudioServer.set_bus_mute(bus_idx, volume_linear <= 0.001) func apply_control_settings(): # Sync custom settings with InputMap var mapping = { "move_up": "move_north", "move_down": "move_south", "move_left": "move_west", "move_right": "move_east", "grab": "action_grab", "put": "action_put", "use_powerup": "use_powerup", "tekton_grab": "action_grab_tekton", "attack_mode": "action_knock_tekton" } for setting_key in mapping.keys(): var action_name = mapping[setting_key] if not InputMap.has_action(action_name): InputMap.add_action(action_name) InputMap.action_erase_events(action_name) # Add Primary var primary_key = settings.controls.get(setting_key) if primary_key: var event = InputEventKey.new() event.keycode = primary_key InputMap.action_add_event(action_name, event) # Add Secondary var secondary_key = settings.controls.get(setting_key + "_alt") if secondary_key: var event = InputEventKey.new() event.keycode = secondary_key InputMap.action_add_event(action_name, event) # Add Joypad defaults for movement if action_name.begins_with("move_"): var joy_axis = -1 var axis_val = 0.0 match action_name: "move_north": joy_axis = JOY_AXIS_LEFT_Y; axis_val = -1.0 "move_south": joy_axis = JOY_AXIS_LEFT_Y; axis_val = 1.0 "move_west": joy_axis = JOY_AXIS_LEFT_X; axis_val = -1.0 "move_east": joy_axis = JOY_AXIS_LEFT_X; axis_val = 1.0 if joy_axis != -1: var joy_event = InputEventJoypadMotion.new() joy_event.axis = joy_axis joy_event.axis_value = axis_val InputMap.action_add_event(action_name, joy_event) func set_control(action_name: String, keycode: int): if settings.controls.has(action_name): settings.controls[action_name] = keycode apply_control_settings() # Apply immediately emit_signal("control_remapped", action_name, keycode) save_settings() func get_control_keycode(action_name: String) -> int: return settings.controls.get(action_name, -1) func get_control_text(action_name: String) -> String: var code = get_control_keycode(action_name) if code == -1: return "Unbound" return OS.get_keycode_string(code)