diff --git a/Rhythia.csproj b/Rhythia.csproj index 94da00f6..ced6fcc4 100644 --- a/Rhythia.csproj +++ b/Rhythia.csproj @@ -20,4 +20,3 @@ - diff --git a/scenes/main.tscn b/scenes/main.tscn index 4a9b0bfe..317c889a 100644 --- a/scenes/main.tscn +++ b/scenes/main.tscn @@ -130,7 +130,7 @@ handle_input_locally = false size = Vector2i(1280, 720) render_target_update_mode = 4 -[node name="Settings" type="ColorRect" parent="." unique_id=1393582030] +[node name="Settings" type="ColorRect" parent="." unique_id=1393582030 node_paths=PackedStringArray("ImportNightlyDialog")] visible = false z_index = 1 anchors_preset = 15 @@ -141,6 +141,7 @@ grow_vertical = 2 mouse_filter = 2 color = Color(0, 0, 0, 0.501961) script = ExtResource("2_o6xl0") +ImportNightlyDialog = NodePath("ImportNightlyDialog") [node name="Hide" type="Button" parent="Settings" unique_id=1153372742] self_modulate = Color(1, 1, 1, 0) @@ -429,6 +430,13 @@ grow_vertical = 2 mouse_filter = 2 theme_override_styles/panel = SubResource("StyleBoxFlat_popqt") +[node name="ImportNightlyDialog" type="FileDialog" parent="Settings" unique_id=1831752407] +title = "Open a File" +file_mode = 0 +access = 2 +filters = PackedStringArray("*.json") +use_native_dialog = true + [node name="Volume" type="Panel" parent="." unique_id=1219254705] modulate = Color(1, 1, 1, 0) z_index = 1 diff --git a/scenes/main_menu.tscn b/scenes/main_menu.tscn index 8801bb64..46331bb9 100644 --- a/scenes/main_menu.tscn +++ b/scenes/main_menu.tscn @@ -1730,6 +1730,15 @@ bbcode_enabled = true text = "[font_size=20]noobour [color=0f81b7][font_size=16]Contributor" +[node name="xwalfie" type="RichTextLabel" parent="Menus/Extras/Credits/ScrollContainer/VBoxContainer" unique_id=427847284] +modulate = Color(0.2627451, 1, 0.9529412, 1) +layout_mode = 2 +bbcode_enabled = true +text = "[font_size=20]xwalfie +[color=ffffffbb][font_size=16]Contributor" +fit_content = true +horizontal_alignment = 1 + [node name="Outline" type="Panel" parent="Menus/Extras/Credits" unique_id=239136532] material = SubResource("ShaderMaterial_yxnar") layout_mode = 1 diff --git a/scripts/Constants.cs b/scripts/Constants.cs index 580934f3..7f0a19f6 100644 --- a/scripts/Constants.cs +++ b/scripts/Constants.cs @@ -10,6 +10,8 @@ public partial class Constants : Node public static readonly string USER_FOLDER = OS.GetUserDataDir(); + public static readonly string NIGHTLY_FOLDER = $"{Path.GetDirectoryName(USER_FOLDER)}/SoundSpacePlus"; + public static readonly string DEFAULT_MAP_EXT = "phxm"; public static readonly bool TEMP_MAP_MODE = false;//OS.GetCmdlineArgs().Length > 0; diff --git a/scripts/SoundManager.cs b/scripts/SoundManager.cs index 9eb59133..e0fcea83 100644 --- a/scripts/SoundManager.cs +++ b/scripts/SoundManager.cs @@ -375,6 +375,12 @@ public static float ComputeVolumeDb(float volume, float master, float range) return (float)(-80 + range * Math.Pow(volume / 100, 0.1) * Math.Pow(master / 100, 0.1)); } + public static float ComputeVolumeFromDb(float db, float master, float range) + { + if (float.IsNegativeInfinity(db) || master <= 0) return 0; + return (float)Math.Clamp(100 * Math.Pow((db + 80) / (range * Math.Pow(master / 100, 0.1)), 10), 0, 100); + } + public static void UpdateVolume() { var settings = SettingsManager.Instance.Settings; diff --git a/scripts/database/settings/SettingsProfile.cs b/scripts/database/settings/SettingsProfile.cs index dd085036..b4c3e220 100644 --- a/scripts/database/settings/SettingsProfile.cs +++ b/scripts/database/settings/SettingsProfile.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.Json; using Godot; public partial class SettingsProfile @@ -342,11 +343,29 @@ public partial class SettingsProfile /// public SettingsItem DisplayFPS { get; private set; } - // [Order] + [Order] /// /// Import settings from previous (nightly) version /// - // public SettingsItem RhythiaImport { get; private set; } + public SettingsItem ImportNightlyProfile { get; private set; } + + [Order] + /// + /// File dialog for the nightly import + /// + public SettingsItem NightlyImportDialog { get; private set; } + + [Order] + /// + /// Imports meshes from the nightly folder + /// + public SettingsItem ImportNightlyMeshes { get; private set; } + + [Order] + /// + /// Imports colorsets from the nightly folder + /// + public SettingsItem ImportNightlyColorsets { get; private set; } [Order] /// @@ -1030,18 +1049,72 @@ public SettingsProfile() #region Other - // RhythiaImport = new(default) - // { - // Id = "RhythiaImport", - // Title = "Import Nightly Settings", - // Description = "Imports settings from the nightly client", - // Section = SettingsSection.Other, - // Buttons = - // [ - // new() { Title = "Import", Description = "", OnPressed = () => { } } - // ], - // SaveToDisk = false, - // }; + ImportNightlyProfile = new(default) + { + Id = "ImportNightlyProfile", + Title = "Import Nightly Settings", + Description = "Imports settings from the nightly client", + Section = SettingsSection.Other, + Buttons = + [ + new() { Title = "Import", Description = "Automatically import settings from nightly", OnPressed = () => { + + if (Directory.Exists(Constants.NIGHTLY_FOLDER)) { + ImportFromNightlySettings($"{Constants.NIGHTLY_FOLDER}/settings.json"); + } + } } + ], + SaveToDisk = false, + }; + + NightlyImportDialog = new(default) + { + Id = "NightlyImportDialog", + Title = "", // this has no title because its supposed to be a button belonging to the field above + Description = "", + Section = SettingsSection.Other, + Buttons = + [ + new() { Title = "Choose file", Description = "Import manually from a nightly settings file", OnPressed = () => { + SettingsMenu.Instance.ImportNightlyDialog.PopupCentered(); + }} + ], + SaveToDisk = false, + }; + + ImportNightlyMeshes = new(default) + { + Id = "ImportNightlyMeshes", + Title = "Import Nightly Meshes", + Description = "Imports meshes from the nightly folder", + Section = SettingsSection.Other, + Buttons = + [ + new() { Title = "Import Nightly Meshes", Description = "Imports meshes from the nightly folder", OnPressed = () => { + importMeshesFromNightly(); + SettingsManager.Instance.Settings.NoteMesh.List.Values = getAvailableMeshes(); + SettingsMenu.Instance.RefreshList(SettingsManager.Instance.Settings.NoteMesh); + }} + ], + SaveToDisk = false, + }; + + ImportNightlyColorsets = new(default) + { + Id = "ImportNightlyColorsets", + Title = "Import Nightly Colorsets", + Description = "Imports colorsets from the nightly folder", + Section = SettingsSection.Other, + Buttons = + [ + new() { Title = "Import Nightly Colorsets", Description = "Imports colorsets from the nightly folder", OnPressed = () => { + importColorsetsFromNightly(); + SettingsManager.Load(); + SettingsMenu.Instance.RefreshList(SettingsManager.Instance.Settings.NoteColors); + }} + ], + SaveToDisk = false, + }; DisplayFPS = new(true) { @@ -1148,4 +1221,199 @@ private static List getAvailableMeshes() return meshes; } + + public static void ImportFromNightlySettings(string settingsPath) + { + string nightlySettings = File.Exists(settingsPath) ? File.ReadAllText(settingsPath) : null; + + if (nightlySettings == null) + { + ToastNotification.Notify("Nightly settings not found, choose the settings file manually."); + return; + } + + using JsonDocument json = JsonDocument.Parse(nightlySettings); + JsonElement root = json.RootElement; + + T? getSetting(string key) where T : struct + { + if (root.TryGetProperty(key, out JsonElement element)) + { + return element.Deserialize(); + } + + return null; + } + + // needs a separate helper for strings due to the struct constraint in getSetting() + string? getStringSetting(string key) + { + if (root.TryGetProperty(key, out JsonElement element)) + { + return element.GetString(); + } + + return null; + } + + double importVolume(string nightlyKey, double range) + { + double? channelDb = getSetting(nightlyKey); + + if (channelDb == null) + { + return 50; + } + + double masterDb = getSetting("master_volume") ?? 20 * Math.Log10(0.5); + double totalDb = Math.Clamp(masterDb + channelDb.Value, -80, 0); + + return SoundManager.ComputeVolumeFromDb((float)totalDb, 100, (float)range); + } + + SettingsProfile nightlyProfile = new SettingsProfile(); + + Dictionary> conversions = new() + { + // sensitivity scales with fov but in nightly it doesnt + ["Sensitivity"] = () => getSetting("sensitivity") * 2.16 * (70 / (getSetting("fov") ?? 70)) ?? nightlyProfile.Sensitivity, + ["AbsoluteSensitivity"] = () => getSetting("absolute_scale") ?? nightlyProfile.AbsoluteSensitivity, + ["AbsoluteInput"] = () => getSetting("absolute_mode") ?? nightlyProfile.AbsoluteInput, + ["CursorDrift"] = () => getSetting("enable_drift_cursor") ?? nightlyProfile.CursorDrift, + ["ApproachRate"] = () => getSetting("approach_rate") ?? nightlyProfile.ApproachRate, + ["ApproachDistance"] = () => getSetting("spawn_distance") ?? nightlyProfile.ApproachDistance, + ["Pushback"] = () => getSetting("do_note_pushback") ?? nightlyProfile.Pushback, + ["CameraParallax"] = () => getSetting("parallax") * 0.025 ?? nightlyProfile.CameraParallax, + ["HUDParallax"] = () => getSetting("ui_parallax") * 0.025 ?? nightlyProfile.HUDParallax, + ["FoV"] = () => getSetting("fov") ?? nightlyProfile.FoV, + ["Colors"] = () => getStringSetting("selected_colorset") ?? nightlyProfile.NoteColors, + ["NoteMesh"] = () => getStringSetting("selected_mesh") ?? nightlyProfile.NoteMesh, + ["NoteSize"] = () => getSetting("note_size") * 0.875 ?? nightlyProfile.NoteSize, + ["NoteOpacity"] = () => getSetting("note_opacity") ?? nightlyProfile.NoteOpacity, + ["CursorScale"] = () => getSetting("cursor_scale") ?? nightlyProfile.CursorScale, + ["CursorRotation"] = () => getSetting("cursor_spin") ?? nightlyProfile.CursorRotation, + ["CursorTrail"] = () => getSetting("cursor_trail") ?? nightlyProfile.CursorTrail, + ["TrailTime"] = () => getSetting("trail_time") ?? nightlyProfile.TrailTime, + ["TrailDetail"] = () => getSetting("trail_detail") ?? nightlyProfile.TrailDetail, + ["SimpleHUD"] = () => getSetting("simple_hud") ?? nightlyProfile.SimpleHUD, + ["HitPopups"] = () => getSetting("score_popup") ?? nightlyProfile.HitPopups, + ["MissPopups"] = () => getSetting("show_miss_effect") ?? nightlyProfile.MissPopups, + ["Fullscreen"] = () => getSetting("window_fullscreen") ?? nightlyProfile.Fullscreen, + ["FPS"] = () => getSetting("target_fps") ?? nightlyProfile.FPS, + ["VolumeMaster"] = () => 100, + ["VolumeMusic"] = () => importVolume("music_volume", 70), + ["VolumeHitSound"] = () => importVolume("hit_volume", 80), + ["VolumeMissSound"] = () => importVolume("miss_volume", 80), + ["VolumeSFX"] = () => importVolume("fail_volume", 80), + ["EnableHitSound"] = () => getSetting("play_hit_snd") ?? nightlyProfile.EnableHitSound, + ["EnableMissSound"] = () => getSetting("play_miss_snd") ?? nightlyProfile.EnableMissSound, + ["EnableMenuMusic"] = () => getSetting("play_menu_music") ?? nightlyProfile.EnableMenuMusic, + ["AutoplayJukebox"] = () => getSetting("auto_preview_song") ?? nightlyProfile.AutoplayJukebox, + ["LocalOffset"] = () => getSetting("music_offset") ?? nightlyProfile.LocalOffset, + ["RecordReplays"] = () => getSetting("record_replays") ?? nightlyProfile.RecordReplays, + }; + + var settingsById = typeof(SettingsProfile).GetProperties() + .Where(p => typeof(ISettingsItem).IsAssignableFrom(p.PropertyType)) + .Select(p => (ISettingsItem)p.GetValue(nightlyProfile)) + .ToDictionary(item => item.Id); + + foreach (var (id, convert) in conversions) + { + settingsById[id].SetVariant(convert()); + } + + // prevents overriding existing 'nightly' profile + string getProfileName(string baseName) + { + string profilesDir = $"{Constants.USER_FOLDER}/profiles"; + Directory.CreateDirectory(profilesDir); + + string name = baseName; + int suffix = 0; + + while (File.Exists($"{profilesDir}/{name}.json")) + { + suffix++; + name = $"{baseName}-{suffix}"; + } + + return name; + } + + string profileName = getProfileName("nightly"); + string profileJson = SettingsProfileConverter.Serialize(nightlyProfile); + File.WriteAllText($"{Constants.USER_FOLDER}/profiles/{profileName}.json", profileJson); + + SettingsManager.SetCurrentProfile(profileName); + SettingsManager.Load(); + SettingsMenu.Instance.UpdateProfileSelection(); + + ToastNotification.Notify($"Created profile '{profileName}'"); + } + + private static void importColorsetsFromNightly() + { + string nightlyColorsetsDir = $"{Constants.NIGHTLY_FOLDER}/colorsets"; + string colorsetsDir = $"{Constants.USER_FOLDER}/colorsets"; + int importedCount = 0; + + if (!Directory.Exists(nightlyColorsetsDir)) + { + ToastNotification.Notify("The nightly colorsets folder doesn't exist"); + return; + } + + Directory.CreateDirectory(colorsetsDir); + + foreach (string file in Directory.GetFiles(nightlyColorsetsDir)) + { + string fileName = Path.GetFileName(file); + string destinationPath = $"{colorsetsDir}/{fileName}"; + + if (!File.Exists(destinationPath)) + { + File.Copy(file, destinationPath); + importedCount++; + } + } + + ToastNotification.Notify($"Imported {importedCount} colorsets from nightly"); + } + + private static void importMeshesFromNightly() + { + string nightlyMeshesDir = $"{Constants.NIGHTLY_FOLDER}/meshes"; + string meshesDir = $"{Constants.USER_FOLDER}/meshes"; + int importedCount = 0; + + if (!Directory.Exists(nightlyMeshesDir)) + { + ToastNotification.Notify("The nightly meshes folder doesn't exist"); + return; + } + + Directory.CreateDirectory(meshesDir); + + foreach (string file in Directory.GetFiles(nightlyMeshesDir, "*.obj")) + { + string fileName = Path.GetFileName(file); + string destinationPath = $"{meshesDir}/{fileName}"; + + if (!File.Exists(destinationPath)) + { + string mtlFile = Path.ChangeExtension(file, ".mtl"); + string mtlDestinationPath = $"{meshesDir}/{Path.GetFileName(mtlFile)}"; + + File.Copy(file, destinationPath); + if (File.Exists(mtlFile)) + { + File.Copy(mtlFile, mtlDestinationPath); + } + importedCount++; + } + } + + ToastNotification.Notify($"Imported {importedCount} meshes from nightly"); + } } diff --git a/scripts/ui/SettingsMenu.cs b/scripts/ui/SettingsMenu.cs index 1b89c2c5..e013da89 100644 --- a/scripts/ui/SettingsMenu.cs +++ b/scripts/ui/SettingsMenu.cs @@ -22,6 +22,8 @@ public partial class SettingsMenu : ColorRect private ScrollContainer selectedCategory; + [Export] public FileDialog ImportNightlyDialog; + public override void _Ready() { Instance = this; @@ -53,7 +55,7 @@ public override void _Ready() SettingsManager.SetCurrentProfile(profile); SettingsManager.Reload(); - updateProfileSelection(); + UpdateProfileSelection(); }; profilesButton.ItemSelected += (index) => @@ -67,7 +69,7 @@ public override void _Ready() SettingsManager.Load(); }; - updateProfileSelection(); + UpdateProfileSelection(); Panel settingTemplate = categoryTemplate.GetNode("Container").GetNode("SettingTemplate"); CheckButton checkButtonTemplate = settingTemplate.GetNode("CheckButton"); @@ -189,6 +191,7 @@ public override void _Ready() HideMenu(); hideButton.Pressed += HideMenu; + ImportNightlyDialog.FileSelected += SettingsProfile.ImportFromNightlySettings; } // Adding GetViewport().SetInputAsHandled() will prevent the Quit popup from appearing when clicking ESC in settings public override void _Input(InputEvent @event) @@ -248,7 +251,7 @@ public void SelectCategory(ScrollContainer category) sidebar.GetNode(new(selectedCategory.Name)).Color = Color.Color8(255, 255, 255, 8); } - private void updateProfileSelection() + public void UpdateProfileSelection() { // skip default for (int i = 1; i < profilesButton.ItemCount; i++) @@ -431,4 +434,15 @@ private void setupButton(SettingsButton setting, Button button) setting.OnPressed?.Invoke(); }; } + + public void RefreshList(ISettingsItem setting) + { + OptionButton optionButton = settingPanels[setting.Id].GetNode("OptionButton"); + optionButton.Clear(); + + foreach (Variant item in setting.List.Values) + { + optionButton.AddItem(item.AsString()); + } + } }