diff --git a/README.md b/README.md index 7180927..cc68a17 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,11 @@ Ett 2D spel byggt i Godot med C# som innehåller en startmeny, timer och save/lo - **Timer** som räknar speltid i övre högra hörnet - **Pausmeny** med knapp i övre vänstra hörnet som innehåller: - Pause/Resume funktionalitet - - Save Game + - Save Game (med anpassade save-namn) - Exit to Main Menu - Exit to Desktop -- **Save/Load System** som sparar speltid i `saves/` mappen -- **Load Game Menu** som visar alla sparade spel med datum +- **Save/Load System** som sparar speltid och anpassade namn +- **Load Game Menu** som visar alla sparade spel med anpassade namn, speltid och datum - Load Game knappen är automatiskt utgråad om inga sparade spel finns ## Krav och Dependencies @@ -76,8 +76,11 @@ Under **Dotnet** sektionen: 2. Välj destination och filnamn (t.ex. `TheGame.exe`) 3. Klicka **Save** +**Viktigt**: Språkfiler (`resources/languages/*.json`) inkluderas automatiskt i exporten! + #### Steg 5: Testa EXE-filen - Den skapade `.exe` filen kan köras direkt på Windows +- Språkväxling fungerar direkt i den exporterade versionen - `saves/` mappen kommer att skapas bredvid exe-filen automatiskt ### Felsökning Export Problem @@ -111,17 +114,27 @@ godot --headless --export-release "Windows Desktop" TheGame.exe TheGame/ ├── project.godot # Godot projektfil ├── TheGame.csproj # C# projektfil +├── default_bus_layout.tres # Audio bus konfiguration ├── scenes/ # Godot scener │ ├── MainMenu.tscn # Huvudmeny │ ├── Game.tscn # Spelscen -│ └── LoadGameMenu.tscn # Ladda spel meny +│ ├── LoadGameMenu.tscn # Ladda spel meny +│ └── SettingsMenu.tscn # Inställningar meny ├── scripts/ # C# scripts │ ├── MainMenu.cs # Huvudmeny logik │ ├── Game.cs # Spel logik och timer │ ├── PauseMenu.cs # Pausmeny logik │ ├── SaveManager.cs # Save/Load hantering │ ├── LoadGameMenu.cs # Ladda spel meny -│ └── GameState.cs # Spelstatus hantering +│ ├── SaveDialog.cs # Save dialog +│ ├── GameState.cs # Spelstatus hantering +│ ├── GameSettings.cs # Inställningar hantering +│ ├── LocalizationManager.cs # Språkhantering +│ └── SettingsMenu.cs # Inställningar meny logik +├── resources/ # Resurser som inkluderas vid export +│ └── languages/ # Språkfiler +│ ├── eng.json # Engelska översättningar +│ └── sve.json # Svenska översättningar ├── saves/ # Sparade spel (skapas automatiskt) └── README.md # Denna fil ``` @@ -131,6 +144,25 @@ TheGame/ - **ESC** - Pausa/återuppta spelet - **Musklick** - Navigera menyer och knappar - **Pausknapp** (☰) - Pausa spelet (övre vänstra hörnet) +- **Enter** - Bekräfta save-namn i save dialog + +## Save/Load System + +### Spara Spel +1. Pausa spelet (ESC eller pausknapp) +2. Klicka "Save Game" +3. Ange ett anpassat namn för ditt save (eller använd standard-namnet) +4. Tryck Enter eller klicka OK + +### Ladda Spel +1. Från huvudmenyn, klicka "Load Game" +2. Välj det save du vill ladda från listan +3. Spelet kommer att fortsätta från den sparade tiden + +Save-filer innehåller: +- **Anpassat namn** (som du angav) +- **Speltid** (timer-status) +- **Datum och tid** när spelet sparades ## Felsökning @@ -152,6 +184,31 @@ TheGame/ - Detta är förväntat beteende om inga sparfiler finns - Knappen blir automatiskt klickbar när du har sparat minst ett spel +5. **Språk växlar inte** + - Kontrollera att språkfilen finns i `resources/languages/` + - Kolla Godot's output för felmeddelanden + - Engelska används som fallback om språkfil saknas + +## 🌍 Lägg Till Nya Språk + +### Skapa Ny Språkfil: +1. Skapa ny `.json` fil i `resources/languages/` +2. Exempel: `ger.json` för tyska, `fra.json` för franska +3. Kopiera struktur från `eng.json` +4. Översätt alla textsträngar +5. Språket dyker automatiskt upp i settings! + +### Rekommenderade Språkkoder: +- `eng` - English +- `sve` - Svenska +- `ger` - Deutsch (German) +- `fra` - Français (French) +- `spa` - Español (Spanish) +- `ita` - Italiano (Italian) +- `jpn` - 日本語 (Japanese) + +**✅ Viktigt**: Språkfiler i `resources/languages/` inkluderas automatiskt vid export! + ## Utveckling För att utveckla projektet vidare: diff --git a/default_bus_layout.tres b/default_bus_layout.tres new file mode 100644 index 0000000..b2e0cdd --- /dev/null +++ b/default_bus_layout.tres @@ -0,0 +1,21 @@ +[gd_resource type="AudioBusLayout" load_steps=0 format=3 uid="uid://bywagvqncwpol"] + +[resource] +bus/1/name = &"Background" +bus/1/solo = false +bus/1/mute = false +bus/1/bypass_fx = false +bus/1/volume_db = 0.0 +bus/1/send = &"Master" +bus/2/name = &"Effects" +bus/2/solo = false +bus/2/mute = false +bus/2/bypass_fx = false +bus/2/volume_db = 0.0 +bus/2/send = &"Master" +bus/3/name = &"Radio" +bus/3/solo = false +bus/3/mute = false +bus/3/bypass_fx = false +bus/3/volume_db = 0.0 +bus/3/send = &"Master" \ No newline at end of file diff --git a/project.godot b/project.godot index a56cb0e..c721e42 100644 --- a/project.godot +++ b/project.godot @@ -15,6 +15,10 @@ run/main_scene="res://scenes/MainMenu.tscn" config/features=PackedStringArray("4.5", "C#", "Forward Plus") config/icon="res://icon.svg" +[audio] + +buses/default_bus_layout="res://default_bus_layout.tres" + [display] window/size/viewport_width=1280 diff --git a/resources/languages/eng.json b/resources/languages/eng.json new file mode 100644 index 0000000..6f29726 --- /dev/null +++ b/resources/languages/eng.json @@ -0,0 +1,51 @@ +{ + "main_menu_title": "THE GAME", + "main_menu_start": "Start Game", + "main_menu_load": "Load Game", + "main_menu_settings": "Settings", + "main_menu_exit": "Exit", + + "pause_menu_title": "PAUSED", + "pause_menu_resume": "Resume", + "pause_menu_save": "Save Game", + "pause_menu_settings": "Settings", + "pause_menu_main_menu": "Exit to Main Menu", + "pause_menu_desktop": "Exit to Desktop", + + "load_game_title": "LOAD GAME", + "load_game_back": "Back to Main Menu", + "load_game_no_saves": "No saved games found", + "load_game_playtime": "Playtime", + "load_game_saved": "Saved", + + "save_dialog_title": "Save Game", + "save_dialog_enter_name": "Enter save name:", + "save_dialog_placeholder": "My Save Game", + + "settings_title": "SETTINGS", + "settings_general": "General", + "settings_video": "Video", + "settings_audio": "Audio", + "settings_apply": "Apply", + "settings_cancel": "Cancel", + "settings_back": "Back", + + "settings_language": "Language", + + "settings_fullscreen": "Fullscreen", + + "settings_master_volume": "Master Volume", + "settings_background_volume": "Background Volume", + "settings_effects_volume": "Effects Volume", + "settings_radio_volume": "Radio Volume", + + "game_content": "GAME CONTENT HERE\\nPress ESC or click pause button to pause", + + "exit_warning_title": "Unsaved Progress", + "exit_warning_message": "You have unsaved progress. If you continue, all unsaved progress will be lost.\\n\\nDo you want to exit anyway?", + + "ok": "OK", + "cancel": "Cancel", + "yes": "Yes", + "no": "No" +} \ No newline at end of file diff --git a/resources/languages/sve.json b/resources/languages/sve.json new file mode 100644 index 0000000..d6fc65d --- /dev/null +++ b/resources/languages/sve.json @@ -0,0 +1,51 @@ +{ + "main_menu_title": "SPELET", + "main_menu_start": "Starta Spel", + "main_menu_load": "Ladda Spel", + "main_menu_settings": "Inställningar", + "main_menu_exit": "Avsluta", + + "pause_menu_title": "PAUSAD", + "pause_menu_resume": "Fortsätt", + "pause_menu_save": "Spara Spel", + "pause_menu_settings": "Inställningar", + "pause_menu_main_menu": "Tillbaka till Huvudmeny", + "pause_menu_desktop": "Avsluta till Skrivbord", + + "load_game_title": "LADDA SPEL", + "load_game_back": "Tillbaka till Huvudmeny", + "load_game_no_saves": "Inga sparade spel hittades", + "load_game_playtime": "Speltid", + "load_game_saved": "Sparat", + + "save_dialog_title": "Spara Spel", + "save_dialog_enter_name": "Ange sparnamn:", + "save_dialog_placeholder": "Mitt Sparade Spel", + + "settings_title": "INSTÄLLNINGAR", + "settings_general": "Allmänt", + "settings_video": "Video", + "settings_audio": "Ljud", + "settings_apply": "Tillämpa", + "settings_cancel": "Avbryt", + "settings_back": "Tillbaka", + + "settings_language": "Språk", + + "settings_fullscreen": "Helskärm", + + "settings_master_volume": "Huvudvolym", + "settings_background_volume": "Bakgrundsljud", + "settings_effects_volume": "Effekter", + "settings_radio_volume": "Radio", + + "game_content": "SPELINNEHÅLL HÄR\\nTryck ESC eller klicka på pausknappen för att pausa", + + "exit_warning_title": "Osparad Framsteg", + "exit_warning_message": "Du har osparad framsteg. Om du fortsätter kommer all osparad framsteg att förloras.\\n\\nVill du avsluta ändå?", + + "ok": "OK", + "cancel": "Avbryt", + "yes": "Ja", + "no": "Nej" +} \ No newline at end of file diff --git a/scenes/Game.tscn b/scenes/Game.tscn index d72042b..fd646f0 100644 --- a/scenes/Game.tscn +++ b/scenes/Game.tscn @@ -68,6 +68,7 @@ layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 +process_mode = 3 script = ExtResource("2_a5qiy") [node name="Background" type="ColorRect" parent="PauseMenu"] @@ -105,6 +106,10 @@ text = "Resume" layout_mode = 2 text = "Save Game" +[node name="SettingsButton" type="Button" parent="PauseMenu/MenuContainer"] +layout_mode = 2 +text = "Settings" + [node name="MainMenuButton" type="Button" parent="PauseMenu/MenuContainer"] layout_mode = 2 text = "Exit to Main Menu" @@ -116,5 +121,6 @@ text = "Exit to Desktop" [connection signal="pressed" from="UI/PauseButton" to="." method="_on_pause_button_pressed"] [connection signal="pressed" from="PauseMenu/MenuContainer/ResumeButton" to="PauseMenu" method="_on_resume_button_pressed"] [connection signal="pressed" from="PauseMenu/MenuContainer/SaveButton" to="PauseMenu" method="_on_save_button_pressed"] +[connection signal="pressed" from="PauseMenu/MenuContainer/SettingsButton" to="PauseMenu" method="_on_settings_button_pressed"] [connection signal="pressed" from="PauseMenu/MenuContainer/MainMenuButton" to="PauseMenu" method="_on_main_menu_button_pressed"] [connection signal="pressed" from="PauseMenu/MenuContainer/DesktopButton" to="PauseMenu" method="_on_desktop_button_pressed"] \ No newline at end of file diff --git a/scenes/MainMenu.tscn b/scenes/MainMenu.tscn index 7c60281..1f442a1 100644 --- a/scenes/MainMenu.tscn +++ b/scenes/MainMenu.tscn @@ -45,10 +45,15 @@ text = "Start Game" layout_mode = 2 text = "Load Game" +[node name="SettingsButton" type="Button" parent="VBoxContainer"] +layout_mode = 2 +text = "Settings" + [node name="ExitButton" type="Button" parent="VBoxContainer"] layout_mode = 2 text = "Exit" [connection signal="pressed" from="VBoxContainer/StartButton" to="." method="_on_start_button_pressed"] [connection signal="pressed" from="VBoxContainer/LoadButton" to="." method="_on_load_button_pressed"] +[connection signal="pressed" from="VBoxContainer/SettingsButton" to="." method="_on_settings_button_pressed"] [connection signal="pressed" from="VBoxContainer/ExitButton" to="." method="_on_exit_button_pressed"] \ No newline at end of file diff --git a/scenes/SettingsMenu.tscn b/scenes/SettingsMenu.tscn new file mode 100644 index 0000000..bc7fb82 --- /dev/null +++ b/scenes/SettingsMenu.tscn @@ -0,0 +1,193 @@ +[gd_scene load_steps=2 format=3 uid="uid://b2k5vxrj8nq4h"] + +[ext_resource type="Script" path="res://scripts/SettingsMenu.cs" id="1_settings"] + +[node name="SettingsMenu" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +process_mode = 3 +script = ExtResource("1_settings") + +[node name="Background" type="ColorRect" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +color = Color(0.15, 0.15, 0.25, 1) + +[node name="MainContainer" type="VBoxContainer" parent="."] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -400.0 +offset_top = -300.0 +offset_right = 400.0 +offset_bottom = 300.0 + +[node name="TitleLabel" type="Label" parent="MainContainer"] +layout_mode = 2 +text = "SETTINGS" +horizontal_alignment = 1 + +[node name="HSeparator" type="HSeparator" parent="MainContainer"] +layout_mode = 2 + +[node name="TabContainer" type="TabContainer" parent="MainContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="General" type="Control" parent="MainContainer/TabContainer"] +layout_mode = 2 + +[node name="GeneralVBox" type="VBoxContainer" parent="MainContainer/TabContainer/General"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 20.0 +offset_top = 20.0 +offset_right = -20.0 +offset_bottom = -20.0 + +[node name="LanguageContainer" type="HBoxContainer" parent="MainContainer/TabContainer/General/GeneralVBox"] +layout_mode = 2 + +[node name="LanguageLabel" type="Label" parent="MainContainer/TabContainer/General/GeneralVBox/LanguageContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Language:" +vertical_alignment = 1 + +[node name="LanguageOption" type="OptionButton" parent="MainContainer/TabContainer/General/GeneralVBox/LanguageContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Video" type="Control" parent="MainContainer/TabContainer"] +layout_mode = 2 + +[node name="VideoVBox" type="VBoxContainer" parent="MainContainer/TabContainer/Video"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 20.0 +offset_top = 20.0 +offset_right = -20.0 +offset_bottom = -20.0 + +[node name="FullscreenContainer" type="HBoxContainer" parent="MainContainer/TabContainer/Video/VideoVBox"] +layout_mode = 2 + +[node name="FullscreenLabel" type="Label" parent="MainContainer/TabContainer/Video/VideoVBox/FullscreenContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Fullscreen:" +vertical_alignment = 1 + +[node name="FullscreenCheck" type="CheckBox" parent="MainContainer/TabContainer/Video/VideoVBox/FullscreenContainer"] +layout_mode = 2 + +[node name="Audio" type="Control" parent="MainContainer/TabContainer"] +layout_mode = 2 + +[node name="AudioVBox" type="VBoxContainer" parent="MainContainer/TabContainer/Audio"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 20.0 +offset_top = 20.0 +offset_right = -20.0 +offset_bottom = -20.0 + +[node name="MasterContainer" type="VBoxContainer" parent="MainContainer/TabContainer/Audio/AudioVBox"] +layout_mode = 2 + +[node name="MasterLabel" type="Label" parent="MainContainer/TabContainer/Audio/AudioVBox/MasterContainer"] +layout_mode = 2 +text = "Master Volume: 100" + +[node name="MasterSlider" type="HSlider" parent="MainContainer/TabContainer/Audio/AudioVBox/MasterContainer"] +layout_mode = 2 +min_value = 1.0 +max_value = 100.0 +step = 1.0 +value = 100.0 + +[node name="BackgroundContainer" type="VBoxContainer" parent="MainContainer/TabContainer/Audio/AudioVBox"] +layout_mode = 2 + +[node name="BackgroundLabel" type="Label" parent="MainContainer/TabContainer/Audio/AudioVBox/BackgroundContainer"] +layout_mode = 2 +text = "Background Volume: 100" + +[node name="BackgroundSlider" type="HSlider" parent="MainContainer/TabContainer/Audio/AudioVBox/BackgroundContainer"] +layout_mode = 2 +min_value = 1.0 +max_value = 100.0 +step = 1.0 +value = 100.0 + +[node name="EffectsContainer" type="VBoxContainer" parent="MainContainer/TabContainer/Audio/AudioVBox"] +layout_mode = 2 + +[node name="EffectsLabel" type="Label" parent="MainContainer/TabContainer/Audio/AudioVBox/EffectsContainer"] +layout_mode = 2 +text = "Effects Volume: 100" + +[node name="EffectsSlider" type="HSlider" parent="MainContainer/TabContainer/Audio/AudioVBox/EffectsContainer"] +layout_mode = 2 +min_value = 1.0 +max_value = 100.0 +step = 1.0 +value = 100.0 + +[node name="RadioContainer" type="VBoxContainer" parent="MainContainer/TabContainer/Audio/AudioVBox"] +layout_mode = 2 + +[node name="RadioLabel" type="Label" parent="MainContainer/TabContainer/Audio/AudioVBox/RadioContainer"] +layout_mode = 2 +text = "Radio Volume: 100" + +[node name="RadioSlider" type="HSlider" parent="MainContainer/TabContainer/Audio/AudioVBox/RadioContainer"] +layout_mode = 2 +min_value = 1.0 +max_value = 100.0 +step = 1.0 +value = 100.0 + +[node name="HSeparator2" type="HSeparator" parent="MainContainer"] +layout_mode = 2 + +[node name="ButtonContainer" type="HBoxContainer" parent="MainContainer"] +layout_mode = 2 + +[node name="ApplyButton" type="Button" parent="MainContainer/ButtonContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Apply" + +[node name="CancelButton" type="Button" parent="MainContainer/ButtonContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Cancel" + +[node name="BackButton" type="Button" parent="MainContainer/ButtonContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Back" + +[connection signal="item_selected" from="MainContainer/TabContainer/General/GeneralVBox/LanguageContainer/LanguageOption" to="." method="_on_language_option_item_selected"] +[connection signal="toggled" from="MainContainer/TabContainer/Video/VideoVBox/FullscreenContainer/FullscreenCheck" to="." method="_on_fullscreen_check_toggled"] +[connection signal="value_changed" from="MainContainer/TabContainer/Audio/AudioVBox/MasterContainer/MasterSlider" to="." method="_on_master_slider_value_changed"] +[connection signal="value_changed" from="MainContainer/TabContainer/Audio/AudioVBox/BackgroundContainer/BackgroundSlider" to="." method="_on_background_slider_value_changed"] +[connection signal="value_changed" from="MainContainer/TabContainer/Audio/AudioVBox/EffectsContainer/EffectsSlider" to="." method="_on_effects_slider_value_changed"] +[connection signal="value_changed" from="MainContainer/TabContainer/Audio/AudioVBox/RadioContainer/RadioSlider" to="." method="_on_radio_slider_value_changed"] +[connection signal="pressed" from="MainContainer/ButtonContainer/ApplyButton" to="." method="_on_apply_button_pressed"] +[connection signal="pressed" from="MainContainer/ButtonContainer/CancelButton" to="." method="_on_cancel_button_pressed"] +[connection signal="pressed" from="MainContainer/ButtonContainer/BackButton" to="." method="_on_back_button_pressed"] \ No newline at end of file diff --git a/scripts/AdvancedSaveDialog.cs b/scripts/AdvancedSaveDialog.cs new file mode 100644 index 0000000..1acee9a --- /dev/null +++ b/scripts/AdvancedSaveDialog.cs @@ -0,0 +1,302 @@ +using Godot; + +namespace TheGame +{ + public partial class AdvancedSaveDialog : AcceptDialog + { + private LineEdit _nameInput; + private CheckBox _newInstanceCheck; + private Label _infoLabel; + private Game _gameController; + private SaveData _existingSaveData; + + public override void _Ready() + { + // Sätt process mode så att dialogen fungerar när spelet är pausat + ProcessMode = ProcessModeEnum.WhenPaused; + + var loc = LocalizationManager.Instance; + Title = loc.GetText("save_dialog_title"); + + // Kontrollera om det finns befintlig save för denna seed + var saveManager = new SaveManager(); + if (GameState.CurrentGameSeed != null) + { + _existingSaveData = saveManager.FindSaveDataBySeed(GameState.CurrentGameSeed); + } + + CreateDialogContent(); + + // Sätt process mode på alla child nodes + SetProcessModeRecursive(this, ProcessModeEnum.WhenPaused); + } + + private void CreateDialogContent() + { + var vbox = new VBoxContainer(); + + if (_existingSaveData == null || _existingSaveData.Instances.Count == 0) + { + // Första gången man sparar detta spel - bara namn input + CreateFirstTimeSaveUI(vbox); + } + else + { + // Det finns befintliga saves - visa alternativ + CreateExistingSaveUI(vbox); + } + + AddChild(vbox); + } + + private void CreateFirstTimeSaveUI(VBoxContainer vbox) + { + var loc = LocalizationManager.Instance; + + var infoLabel = new Label(); + infoLabel.Text = "This is your first save for this game."; + infoLabel.AutowrapMode = TextServer.AutowrapMode.WordSmart; + vbox.AddChild(infoLabel); + + var separator = new HSeparator(); + vbox.AddChild(separator); + + // Save name input + var nameLabel = new Label(); + nameLabel.Text = loc.GetText("save_dialog_enter_name"); + vbox.AddChild(nameLabel); + + _nameInput = new LineEdit(); + _nameInput.PlaceholderText = loc.GetText("save_dialog_placeholder"); + _nameInput.Text = $"Save {System.DateTime.Now:HH:mm:ss}"; + vbox.AddChild(_nameInput); + + // Anslut signaler + Confirmed += OnSaveConfirmed; + + // Fokusera på text input + _nameInput.GrabFocus(); + _nameInput.SelectAll(); + } + + private void CreateExistingSaveUI(VBoxContainer vbox) + { + var latestInstance = _existingSaveData.GetLatestInstance(); + + var infoLabel = new Label(); + infoLabel.Text = $"Game Seed: {_existingSaveData.GameSeed.ToString()}\nExisting saves: {_existingSaveData.Instances.Count}\nLatest: {latestInstance.DisplayName} ({latestInstance.GetFormattedGameTime()})"; + infoLabel.AutowrapMode = TextServer.AutowrapMode.WordSmart; + vbox.AddChild(infoLabel); + + var separator = new HSeparator(); + vbox.AddChild(separator); + + // Button för att skriva över senaste save + var overwriteButton = new Button(); + overwriteButton.Text = "Save over the last save point"; + overwriteButton.CustomMinimumSize = new Vector2(0, 40); + overwriteButton.Pressed += OnOverwriteLatestPressed; + vbox.AddChild(overwriteButton); + + // Button för att skapa ny save point + var newSaveButton = new Button(); + newSaveButton.Text = "Save a new save point"; + newSaveButton.CustomMinimumSize = new Vector2(0, 40); + newSaveButton.Pressed += OnNewSavePressed; + vbox.AddChild(newSaveButton); + + // Dölj standard OK-knappen + GetOkButton().Visible = false; + } + + public void Initialize(Game gameController) + { + _gameController = gameController; + } + + private void ShowErrorDialog(string message) + { + var errorDialog = new AcceptDialog(); + errorDialog.DialogText = message; + errorDialog.Title = "Save Error"; + errorDialog.ProcessMode = ProcessModeEnum.WhenPaused; + GetTree().Root.AddChild(errorDialog); + errorDialog.PopupCentered(); + } + + private void OnOverwriteLatestPressed() + { + // Skriv över senaste save utan att fråga om namn + if (_gameController != null) + { + var saveManager = new SaveManager(); + float currentTime = _gameController.GetGameTime(); + + bool saveSuccess = saveManager.SaveGame(currentTime, "", GameState.CurrentGameSeed, true); // overwriteLatest = true + + if (saveSuccess) + { + GameState.MarkProgressAsSaved(currentTime); + _gameController.TogglePause(); + } + else + { + ShowErrorDialog("Failed to save the game. Please try again."); + return; + } + } + + QueueFree(); + } + + private void OnNewSavePressed() + { + // Visa namn-input dialog för ny save point + ShowNameInputDialog(); + } + + private void ShowNameInputDialog() + { + // Rensa nuvarande innehåll + foreach (Node child in GetChildren()) + { + if (child != GetOkButton()) + { + child.QueueFree(); + } + } + + var vbox = new VBoxContainer(); + + var loc = LocalizationManager.Instance; + + var nameLabel = new Label(); + nameLabel.Text = loc.GetText("save_dialog_enter_name"); + vbox.AddChild(nameLabel); + + _nameInput = new LineEdit(); + _nameInput.PlaceholderText = loc.GetText("save_dialog_placeholder"); + _nameInput.Text = $"Save {System.DateTime.Now:HH:mm:ss}"; + vbox.AddChild(_nameInput); + + AddChild(vbox); + + // Visa OK-knappen igen och anslut till ny save + GetOkButton().Visible = true; + + // Ta bort tidigare signaler och lägg till ny + if (IsConnected("confirmed", new Callable(this, nameof(OnSaveConfirmed)))) + { + Disconnect("confirmed", new Callable(this, nameof(OnSaveConfirmed))); + } + Confirmed += OnNewSaveConfirmed; + + // Fokusera på text input + _nameInput.GrabFocus(); + _nameInput.SelectAll(); + } + + private void OnNewSaveConfirmed() + { + if (_gameController != null) + { + string saveName = _nameInput.Text.Trim(); + if (string.IsNullOrEmpty(saveName)) + { + saveName = $"Save {System.DateTime.Now:HH:mm:ss}"; + } + + var saveManager = new SaveManager(); + float currentTime = _gameController.GetGameTime(); + + bool saveSuccess = saveManager.SaveGame(currentTime, saveName, GameState.CurrentGameSeed, false); // overwriteLatest = false (ny instans) + + if (saveSuccess) + { + GameState.MarkProgressAsSaved(currentTime); + _gameController.TogglePause(); + } + else + { + ShowErrorDialog("Failed to save the game. Please try again."); + return; + } + } + + QueueFree(); + } + + private void OnSaveConfirmed() + { + // För första gången man sparar + if (_gameController != null) + { + string saveName = _nameInput.Text.Trim(); + if (string.IsNullOrEmpty(saveName)) + { + saveName = $"Save {System.DateTime.Now:HH:mm:ss}"; + } + + var saveManager = new SaveManager(); + float currentTime = _gameController.GetGameTime(); + + bool saveSuccess = saveManager.SaveGame(currentTime, saveName, GameState.CurrentGameSeed, false); // Första save är alltid ny instans + + if (saveSuccess) + { + GameState.MarkProgressAsSaved(currentTime); + _gameController.TogglePause(); + } + else + { + ShowErrorDialog("Failed to save the game. Please try again."); + return; + } + } + + QueueFree(); + } + + private void SetProcessModeRecursive(Node node, ProcessModeEnum mode) + { + if (node is Control control) + { + control.ProcessMode = mode; + } + + foreach (Node child in node.GetChildren()) + { + SetProcessModeRecursive(child, mode); + } + } + + public override void _Input(InputEvent @event) + { + if (@event is InputEventKey keyEvent && keyEvent.Pressed) + { + if (keyEvent.Keycode == Key.Enter || keyEvent.Keycode == Key.KpEnter) + { + // Hantera Enter baserat på nuvarande state + if (_nameInput != null && _nameInput.Visible) + { + if (IsConnected("confirmed", new Callable(this, nameof(OnNewSaveConfirmed)))) + { + OnNewSaveConfirmed(); + } + else + { + OnSaveConfirmed(); + } + } + GetViewport().SetInputAsHandled(); + } + else if (keyEvent.Keycode == Key.Escape) + { + Hide(); + QueueFree(); + GetViewport().SetInputAsHandled(); + } + } + } + } +} \ No newline at end of file diff --git a/scripts/AdvancedSaveDialog.cs.uid b/scripts/AdvancedSaveDialog.cs.uid new file mode 100644 index 0000000..958d52b --- /dev/null +++ b/scripts/AdvancedSaveDialog.cs.uid @@ -0,0 +1 @@ +uid://bbvu2wk22cfb3 diff --git a/scripts/ExitConfirmationDialog.cs b/scripts/ExitConfirmationDialog.cs new file mode 100644 index 0000000..882cde9 --- /dev/null +++ b/scripts/ExitConfirmationDialog.cs @@ -0,0 +1,106 @@ +using Godot; +using System; + +namespace TheGame +{ + public partial class ExitConfirmationDialog : AcceptDialog + { + private Action _onConfirm; + private Button _yesButton; + private Button _noButton; + private Label _messageLabel; + + public override void _Ready() + { + // Lägg till i localized_ui gruppen + AddToGroup("localized_ui"); + + // Skapa dialog layout + SetupDialog(); + UpdateLocalization(); + } + + private void SetupDialog() + { + // Sätt dialog egenskaper + Title = ""; + Unresizable = false; + ProcessMode = ProcessModeEnum.WhenPaused; + + // Ta bort standard OK knapp + GetOkButton().QueueFree(); + + // Skapa innehåll + var vbox = new VBoxContainer(); + AddChild(vbox); + + _messageLabel = new Label(); + _messageLabel.HorizontalAlignment = HorizontalAlignment.Center; + _messageLabel.AutowrapMode = TextServer.AutowrapMode.WordSmart; + vbox.AddChild(_messageLabel); + + // Lägg till mellanrum + var spacer = new Control(); + spacer.CustomMinimumSize = new Vector2(0, 20); + vbox.AddChild(spacer); + + // Skapa knappar + var buttonContainer = new HBoxContainer(); + buttonContainer.Alignment = BoxContainer.AlignmentMode.Center; + vbox.AddChild(buttonContainer); + + _yesButton = new Button(); + _yesButton.Pressed += OnYesPressed; + buttonContainer.AddChild(_yesButton); + + // Lägg till mellanrum mellan knappar + var buttonSpacer = new Control(); + buttonSpacer.CustomMinimumSize = new Vector2(20, 0); + buttonContainer.AddChild(buttonSpacer); + + _noButton = new Button(); + _noButton.Pressed += OnNoPressed; + buttonContainer.AddChild(_noButton); + + // Sätt minimum storlek för dialog + Size = new Vector2I(400, 200); + } + + public void UpdateLocalization() + { + var loc = LocalizationManager.Instance; + Title = loc.GetText("exit_warning_title"); + _messageLabel.Text = loc.GetText("exit_warning_message"); + _yesButton.Text = loc.GetText("yes"); + _noButton.Text = loc.GetText("no"); + } + + public void ShowDialog(Action onConfirm) + { + _onConfirm = onConfirm; + PopupCentered(); + _noButton.GrabFocus(); // Fokusera på "Nej" som säkrare alternativ + } + + private void OnYesPressed() + { + Hide(); + _onConfirm?.Invoke(); + } + + private void OnNoPressed() + { + Hide(); + } + + public override void _Input(InputEvent @event) + { + // Hantera ESC för att stänga dialog (samma som "Nej") + if (@event.IsActionPressed("ui_cancel") && Visible) + { + OnNoPressed(); + GetViewport().SetInputAsHandled(); + } + } + } +} \ No newline at end of file diff --git a/scripts/ExitConfirmationDialog.cs.uid b/scripts/ExitConfirmationDialog.cs.uid new file mode 100644 index 0000000..3c1602e --- /dev/null +++ b/scripts/ExitConfirmationDialog.cs.uid @@ -0,0 +1 @@ +uid://ca1pkqrwj3k4p \ No newline at end of file diff --git a/scripts/Game.cs b/scripts/Game.cs index 83be572..9fbdaba 100644 --- a/scripts/Game.cs +++ b/scripts/Game.cs @@ -6,20 +6,57 @@ namespace TheGame { private Label _timerLabel; private Control _pauseMenu; + private Label _gameContentLabel; private float _gameTime = 0.0f; private bool _isPaused = false; + private ExitConfirmationDialog _exitDialog; public override void _Ready() { + // Lägg till i localized_ui gruppen + AddToGroup("localized_ui"); + _timerLabel = GetNode