using Godot; using System.Collections.Generic; namespace Mr.BrickAdventures.Autoloads; public partial class SettingsManager : Node { public static SettingsManager Instance { get; private set; } public float GamepadDeadzone { get; set; } = 0.2f; public float GamepadSensitivity { get; set; } = 1.0f; public string WindowMode { get; set; } = "fullscreen"; public Vector2I Resolution { get; set; } = new Vector2I(1920, 1080); private static readonly List CuratedResolutions = new() { // 4:3 new Vector2I(640, 480), new Vector2I(800, 600), new Vector2I(1024, 768), new Vector2I(1280, 960), new Vector2I(1600, 1200), // 16:9 new Vector2I(1280, 720), new Vector2I(1366, 768), new Vector2I(1600, 900), new Vector2I(1920, 1080), new Vector2I(2560, 1440), new Vector2I(3840, 2160), // 16:10 new Vector2I(1280, 800), new Vector2I(1440, 900), new Vector2I(1920, 1200), new Vector2I(2560, 1600), }; private readonly List _customResolutions = new(); private static readonly string[] DeadzoneActions = { "left", "right", "up", "down" }; public override void _Ready() { Instance = this; if (ConfigFileHandler.Instance == null) { GD.PushError("SettingsManager: ConfigFileHandler.Instance is null"); return; } var cfg = ConfigFileHandler.Instance.SettingsConfig; // --- display_settings --- WindowMode = cfg.GetValue("display_settings", "window_mode", Variant.From("fullscreen")).AsString(); var resStr = cfg.GetValue("display_settings", "resolution", Variant.From("1920x1080")).AsString(); Resolution = ParseResolution(resStr, new Vector2I(1920, 1080)); var customResStr = cfg.GetValue("display_settings", "custom_resolutions", Variant.From("")).AsString(); if (!string.IsNullOrWhiteSpace(customResStr)) { foreach (var part in customResStr.Split(',')) { var r = ParseResolution(part.Trim(), Vector2I.Zero); if (r != Vector2I.Zero) _customResolutions.Add(r); } } ApplyDisplaySettings(); // --- gameplay_settings --- GamepadDeadzone = (float)cfg.GetValue("gameplay_settings", "gamepad_deadzone", Variant.From(0.2)).AsDouble(); GamepadSensitivity = (float)cfg.GetValue("gameplay_settings", "gamepad_sensitivity", Variant.From(1.0)).AsDouble(); ApplyGamepadDeadzone(); // --- input_settings --- ApplyInputBindings(cfg); } public override void _ExitTree() { if (Instance == this) Instance = null; } // ── public API ─────────────────────────────────────────────────────────── public List GetAllResolutions() { var all = new List(CuratedResolutions); all.AddRange(_customResolutions); return all; } public void SaveDisplaySettings() { var cfg = ConfigFileHandler.Instance.SettingsConfig; cfg.SetValue("display_settings", "window_mode", WindowMode); cfg.SetValue("display_settings", "resolution", $"{Resolution.X}x{Resolution.Y}"); cfg.SetValue("display_settings", "custom_resolutions", BuildCustomResolutionsString()); cfg.Save(ConfigFileHandler.SettingsPath); } public void SaveGameplaySettings() { var cfg = ConfigFileHandler.Instance.SettingsConfig; cfg.SetValue("gameplay_settings", "gamepad_deadzone", (double)GamepadDeadzone); cfg.SetValue("gameplay_settings", "gamepad_sensitivity", (double)GamepadSensitivity); cfg.Save(ConfigFileHandler.SettingsPath); } public void SaveInputSettings() { var cfg = ConfigFileHandler.Instance.SettingsConfig; foreach (var action in InputMap.GetActions()) { if (action.ToString().StartsWith("ui_")) continue; foreach (var ev in InputMap.ActionGetEvents(action)) { if (ev is InputEventKey key) { cfg.SetValue("input_settings", action, (long)key.PhysicalKeycode); break; } } } cfg.Save(ConfigFileHandler.SettingsPath); } // ── apply helpers ──────────────────────────────────────────────────────── private void ApplyDisplaySettings() { switch (WindowMode) { case "fullscreen": DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen); break; case "borderless": DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed); DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, true); DisplayServer.WindowSetSize(Resolution); break; default: // "windowed" DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed); DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false); DisplayServer.WindowSetSize(Resolution); break; } } private void ApplyGamepadDeadzone() { foreach (var action in DeadzoneActions) { if (InputMap.HasAction(action)) InputMap.ActionSetDeadzone(action, GamepadDeadzone); } } private static void ApplyInputBindings(ConfigFile cfg) { if (!cfg.HasSection("input_settings")) return; foreach (var actionName in cfg.GetSectionKeys("input_settings")) { if (actionName.StartsWith("ui_")) continue; if (!InputMap.HasAction(actionName)) continue; var scancode = (Key)(long)cfg.GetValue("input_settings", actionName); // Remove existing keyboard events for this action var events = InputMap.ActionGetEvents(actionName); foreach (var ev in events) { if (ev is InputEventKey) InputMap.ActionEraseEvent(actionName, ev); } // Add the saved key var newKey = new InputEventKey { PhysicalKeycode = scancode }; InputMap.ActionAddEvent(actionName, newKey); } } // ── util ───────────────────────────────────────────────────────────────── private static Vector2I ParseResolution(string s, Vector2I fallback) { var parts = s.Split('x'); if (parts.Length == 2 && int.TryParse(parts[0], out var w) && int.TryParse(parts[1], out var h)) return new Vector2I(w, h); return fallback; } private string BuildCustomResolutionsString() { var parts = new List(); foreach (var r in _customResolutions) parts.Add($"{r.X}x{r.Y}"); return string.Join(",", parts); } }