Add localization support and refactor menus
- Implemented LocalizationManager for handling multiple languages. - Updated LoadGameMenu, MainMenu, PauseMenu, and SettingsMenu to support localization. - Added InstanceSelectionMenu for selecting game instances. - Refactored SaveManager to handle new SaveInstance structure. - Created SaveDialog for saving games with user-defined names. - Enhanced SaveData to manage multiple save instances. - Added error handling and logging for save/load operations. - Updated UI elements to reflect localized text.
This commit is contained in:
67
README.md
67
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:
|
||||
|
||||
21
default_bus_layout.tres
Normal file
21
default_bus_layout.tres
Normal file
@@ -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"
|
||||
@@ -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
|
||||
|
||||
51
resources/languages/eng.json
Normal file
51
resources/languages/eng.json
Normal file
@@ -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"
|
||||
}
|
||||
51
resources/languages/sve.json
Normal file
51
resources/languages/sve.json
Normal file
@@ -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"
|
||||
}
|
||||
@@ -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"]
|
||||
@@ -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"]
|
||||
193
scenes/SettingsMenu.tscn
Normal file
193
scenes/SettingsMenu.tscn
Normal file
@@ -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"]
|
||||
302
scripts/AdvancedSaveDialog.cs
Normal file
302
scripts/AdvancedSaveDialog.cs
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
scripts/AdvancedSaveDialog.cs.uid
Normal file
1
scripts/AdvancedSaveDialog.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bbvu2wk22cfb3
|
||||
106
scripts/ExitConfirmationDialog.cs
Normal file
106
scripts/ExitConfirmationDialog.cs
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
scripts/ExitConfirmationDialog.cs.uid
Normal file
1
scripts/ExitConfirmationDialog.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ca1pkqrwj3k4p
|
||||
@@ -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<Label>("UI/TimerLabel");
|
||||
_pauseMenu = GetNode<Control>("PauseMenu");
|
||||
_gameContentLabel = GetNode<Label>("GameContent");
|
||||
|
||||
// Om vi kommer från Load Game, försök ladda sparat tillstånd
|
||||
// Sätt process mode så att pausmenyn fungerar när spelet är pausat
|
||||
_pauseMenu.ProcessMode = ProcessModeEnum.WhenPaused;
|
||||
|
||||
// Kontrollera om vi laddar ett sparat spel eller startar nytt
|
||||
if (GameState.LoadedGameTime >= 0)
|
||||
{
|
||||
// Laddar befintligt spel
|
||||
_gameTime = GameState.LoadedGameTime;
|
||||
GameState.LastSavedGameTime = _gameTime; // Sätt senaste sparade tid
|
||||
GameState.LoadedGameTime = -1; // Reset efter användning
|
||||
GD.Print($"Loaded existing game with seed: {GameState.CurrentGameSeed?.ToString() ?? "None"}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Nytt spel - skapa ny seed om ingen finns
|
||||
if (GameState.CurrentGameSeed == null)
|
||||
{
|
||||
GameState.CurrentGameSeed = new GameSeed();
|
||||
GD.Print($"Started new game with seed: {GameState.CurrentGameSeed.ToString()}");
|
||||
}
|
||||
GameState.LastSavedGameTime = 0;
|
||||
}
|
||||
|
||||
// Skapa exit confirmation dialog
|
||||
_exitDialog = new ExitConfirmationDialog();
|
||||
AddChild(_exitDialog);
|
||||
|
||||
// Hantera fönsterstängning
|
||||
GetTree().AutoAcceptQuit = false;
|
||||
|
||||
UpdateLocalization();
|
||||
}
|
||||
|
||||
public void UpdateLocalization()
|
||||
{
|
||||
var loc = LocalizationManager.Instance;
|
||||
_gameContentLabel.Text = loc.GetText("game_content");
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
@@ -28,6 +65,36 @@ namespace TheGame
|
||||
{
|
||||
_gameTime += (float)delta;
|
||||
UpdateTimerDisplay();
|
||||
|
||||
// Kontrollera om det finns osparad progress
|
||||
GameState.CheckUnsavedProgress(_gameTime);
|
||||
}
|
||||
}
|
||||
|
||||
public override void _Notification(int what)
|
||||
{
|
||||
if (what == NotificationWMCloseRequest)
|
||||
{
|
||||
// Hantera X-knappen på fönstret
|
||||
HandleExitRequest(() => GetTree().Quit());
|
||||
}
|
||||
}
|
||||
|
||||
public void HandleExitRequest(System.Action exitAction)
|
||||
{
|
||||
if (GameState.HasUnsavedProgress)
|
||||
{
|
||||
// Pausa spelet och visa varning
|
||||
if (!_isPaused)
|
||||
{
|
||||
TogglePause();
|
||||
}
|
||||
_exitDialog.ShowDialog(exitAction);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ingen osparad progress, avsluta direkt
|
||||
exitAction?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
55
scripts/GameSeed.cs
Normal file
55
scripts/GameSeed.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
|
||||
namespace TheGame
|
||||
{
|
||||
public class GameSeed
|
||||
{
|
||||
public string SeedValue { get; set; }
|
||||
public DateTime CreatedDate { get; set; }
|
||||
public string DisplayName { get; set; }
|
||||
|
||||
public GameSeed()
|
||||
{
|
||||
SeedValue = GenerateNewSeed();
|
||||
CreatedDate = DateTime.Now;
|
||||
DisplayName = $"Game {CreatedDate:yyyy-MM-dd HH:mm}";
|
||||
}
|
||||
|
||||
public GameSeed(string customSeed, string displayName = "")
|
||||
{
|
||||
SeedValue = customSeed;
|
||||
CreatedDate = DateTime.Now;
|
||||
DisplayName = string.IsNullOrEmpty(displayName) ? $"Game {CreatedDate:yyyy-MM-dd HH:mm}" : displayName;
|
||||
}
|
||||
|
||||
private static string GenerateNewSeed()
|
||||
{
|
||||
// Generera en 8-tecken seed baserad på timestamp och random
|
||||
var random = new Random();
|
||||
var timestamp = DateTime.Now.Ticks;
|
||||
var combined = timestamp + random.Next(1000, 9999);
|
||||
|
||||
// Konvertera till hexadecimal och ta första 8 tecknen
|
||||
return Math.Abs(combined).ToString("X")[..8];
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{DisplayName} (Seed: {SeedValue})";
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is GameSeed other)
|
||||
{
|
||||
return SeedValue.Equals(other.SeedValue);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return SeedValue.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
1
scripts/GameSeed.cs.uid
Normal file
1
scripts/GameSeed.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bibp6hpk275ab
|
||||
148
scripts/GameSettings.cs
Normal file
148
scripts/GameSettings.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
using Godot;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace TheGame
|
||||
{
|
||||
public class GameSettings
|
||||
{
|
||||
public bool Fullscreen { get; set; } = false;
|
||||
public int MasterVolume { get; set; } = 100;
|
||||
public int BackgroundVolume { get; set; } = 100;
|
||||
public int EffectsVolume { get; set; } = 100;
|
||||
public int RadioVolume { get; set; } = 100;
|
||||
public string Language { get; set; } = "eng";
|
||||
|
||||
private static GameSettings _instance;
|
||||
private static readonly string _settingsPath;
|
||||
|
||||
static GameSettings()
|
||||
{
|
||||
// Spara bredvid binären (executable directory)
|
||||
string executableDir = OS.GetExecutablePath().GetBaseDir();
|
||||
_settingsPath = Path.Combine(executableDir, "settings.json");
|
||||
}
|
||||
|
||||
public static GameSettings Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = LoadSettings();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private static GameSettings LoadSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(_settingsPath))
|
||||
{
|
||||
string jsonString = File.ReadAllText(_settingsPath);
|
||||
var settings = JsonSerializer.Deserialize<GameSettings>(jsonString);
|
||||
if (settings != null)
|
||||
{
|
||||
GD.Print($"Settings loaded from: {_settingsPath}");
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GD.PrintErr($"Failed to load settings: {ex.Message}");
|
||||
}
|
||||
|
||||
// Returnera standardinställningar om fil inte finns eller laddning misslyckas
|
||||
GD.Print("Using default settings");
|
||||
return new GameSettings();
|
||||
}
|
||||
|
||||
public void SaveSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
string jsonString = JsonSerializer.Serialize(this, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
});
|
||||
File.WriteAllText(_settingsPath, jsonString);
|
||||
GD.Print($"Settings saved to: {_settingsPath}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GD.PrintErr($"Failed to save settings: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyVideoSettings()
|
||||
{
|
||||
if (Fullscreen)
|
||||
{
|
||||
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen);
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed);
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyAudioSettings()
|
||||
{
|
||||
// Konvertera från 1-100 till dB (Godot använder dB för ljud)
|
||||
float masterDb = ConvertVolumeToDb(MasterVolume);
|
||||
float backgroundDb = ConvertVolumeToDb(BackgroundVolume);
|
||||
float effectsDb = ConvertVolumeToDb(EffectsVolume);
|
||||
float radioDb = ConvertVolumeToDb(RadioVolume);
|
||||
|
||||
// Sätt bus-volym (dessa måste skapas i Audio Bus Layout)
|
||||
try
|
||||
{
|
||||
AudioServer.SetBusVolumeDb(AudioServer.GetBusIndex("Master"), masterDb);
|
||||
|
||||
int backgroundIndex = AudioServer.GetBusIndex("Background");
|
||||
if (backgroundIndex != -1)
|
||||
AudioServer.SetBusVolumeDb(backgroundIndex, backgroundDb);
|
||||
|
||||
int effectsIndex = AudioServer.GetBusIndex("Effects");
|
||||
if (effectsIndex != -1)
|
||||
AudioServer.SetBusVolumeDb(effectsIndex, effectsDb);
|
||||
|
||||
int radioIndex = AudioServer.GetBusIndex("Radio");
|
||||
if (radioIndex != -1)
|
||||
AudioServer.SetBusVolumeDb(radioIndex, radioDb);
|
||||
|
||||
GD.Print($"Applied audio settings: Master={MasterVolume}, Background={BackgroundVolume}, Effects={EffectsVolume}, Radio={RadioVolume}");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
GD.PrintErr($"Failed to apply audio settings: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private float ConvertVolumeToDb(int volume)
|
||||
{
|
||||
if (volume <= 0)
|
||||
return -80.0f; // Tyst
|
||||
|
||||
// Konvertera 1-100 till -60dB till 0dB
|
||||
return -60.0f + (volume / 100.0f) * 60.0f;
|
||||
}
|
||||
|
||||
public void ApplyLanguageSettings()
|
||||
{
|
||||
LocalizationManager.Instance.SetLanguage(Language);
|
||||
}
|
||||
|
||||
public void ApplyAllSettings()
|
||||
{
|
||||
ApplyVideoSettings();
|
||||
ApplyAudioSettings();
|
||||
ApplyLanguageSettings();
|
||||
SaveSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
1
scripts/GameSettings.cs.uid
Normal file
1
scripts/GameSettings.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c3t2rdrect1lw
|
||||
@@ -5,5 +5,33 @@ namespace TheGame
|
||||
public static class GameState
|
||||
{
|
||||
public static float LoadedGameTime = -1;
|
||||
public static GameSeed CurrentGameSeed = null;
|
||||
public static SaveInstance LoadedInstance = null;
|
||||
public static bool HasUnsavedProgress = false;
|
||||
public static float LastSavedGameTime = 0;
|
||||
|
||||
public static void MarkProgressAsSaved(float gameTime)
|
||||
{
|
||||
LastSavedGameTime = gameTime;
|
||||
HasUnsavedProgress = false;
|
||||
}
|
||||
|
||||
public static void CheckUnsavedProgress(float currentGameTime)
|
||||
{
|
||||
// Om speltiden har ökat sedan senaste sparningen, markera som osparad
|
||||
if (currentGameTime > LastSavedGameTime + 1.0f) // 1 sekund tolerans
|
||||
{
|
||||
HasUnsavedProgress = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ResetGameState()
|
||||
{
|
||||
LoadedGameTime = -1;
|
||||
CurrentGameSeed = null;
|
||||
LoadedInstance = null;
|
||||
HasUnsavedProgress = false;
|
||||
LastSavedGameTime = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
121
scripts/InstanceSelectionMenu.cs
Normal file
121
scripts/InstanceSelectionMenu.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using Godot;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace TheGame
|
||||
{
|
||||
public partial class InstanceSelectionMenu : AcceptDialog
|
||||
{
|
||||
private SaveData _saveData;
|
||||
private VBoxContainer _instanceList;
|
||||
private Button _cancelButton;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
ProcessMode = ProcessModeEnum.Always;
|
||||
Title = "Select Save Instance";
|
||||
|
||||
var vbox = new VBoxContainer();
|
||||
|
||||
// Header info
|
||||
var headerLabel = new Label();
|
||||
headerLabel.Text = $"Game Seed: {_saveData?.GameSeed?.ToString() ?? "Unknown"}";
|
||||
headerLabel.AddThemeStyleboxOverride("normal", new StyleBoxFlat());
|
||||
vbox.AddChild(headerLabel);
|
||||
|
||||
var separator = new HSeparator();
|
||||
vbox.AddChild(separator);
|
||||
|
||||
// Scroll container för instanser
|
||||
var scrollContainer = new ScrollContainer();
|
||||
scrollContainer.CustomMinimumSize = new Vector2(0, 300);
|
||||
|
||||
_instanceList = new VBoxContainer();
|
||||
scrollContainer.AddChild(_instanceList);
|
||||
vbox.AddChild(scrollContainer);
|
||||
|
||||
// Cancel button
|
||||
var buttonContainer = new HBoxContainer();
|
||||
buttonContainer.Alignment = BoxContainer.AlignmentMode.End;
|
||||
|
||||
_cancelButton = new Button();
|
||||
_cancelButton.Text = "Cancel";
|
||||
_cancelButton.Pressed += OnCancelPressed;
|
||||
buttonContainer.AddChild(_cancelButton);
|
||||
|
||||
vbox.AddChild(buttonContainer);
|
||||
|
||||
AddChild(vbox);
|
||||
|
||||
PopulateInstanceList();
|
||||
}
|
||||
|
||||
public void Initialize(SaveData saveData)
|
||||
{
|
||||
_saveData = saveData;
|
||||
}
|
||||
|
||||
private void PopulateInstanceList()
|
||||
{
|
||||
if (_saveData?.Instances == null) return;
|
||||
|
||||
// Sortera instances efter save date (nyast först)
|
||||
var sortedInstances = _saveData.Instances.OrderByDescending(i => i.SaveDate).ToList();
|
||||
|
||||
foreach (var instance in sortedInstances)
|
||||
{
|
||||
var instanceButton = new Button();
|
||||
instanceButton.CustomMinimumSize = new Vector2(0, 60);
|
||||
instanceButton.SizeFlagsHorizontal = Control.SizeFlags.ExpandFill;
|
||||
|
||||
// Skapa text med formatering
|
||||
var instanceText = $"{instance.DisplayName}\n";
|
||||
instanceText += $"Game Time: {instance.GetFormattedGameTime()}\n";
|
||||
instanceText += $"Saved: {instance.SaveDate:yyyy-MM-dd HH:mm:ss}";
|
||||
|
||||
instanceButton.Text = instanceText;
|
||||
instanceButton.Pressed += () => OnInstanceSelected(instance);
|
||||
|
||||
_instanceList.AddChild(instanceButton);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInstanceSelected(SaveInstance instance)
|
||||
{
|
||||
var saveManager = new SaveManager();
|
||||
if (saveManager.LoadGame(_saveData, instance))
|
||||
{
|
||||
GetTree().ChangeSceneToFile("res://scenes/Game.tscn");
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.PrintErr("Failed to load selected game instance");
|
||||
// Visa error message till användaren
|
||||
var errorDialog = new AcceptDialog();
|
||||
errorDialog.DialogText = "Failed to load the selected save instance.";
|
||||
errorDialog.Title = "Load Error";
|
||||
GetTree().Root.AddChild(errorDialog);
|
||||
errorDialog.PopupCentered();
|
||||
}
|
||||
|
||||
QueueFree();
|
||||
}
|
||||
|
||||
private void OnCancelPressed()
|
||||
{
|
||||
QueueFree();
|
||||
}
|
||||
|
||||
public override void _Input(InputEvent @event)
|
||||
{
|
||||
if (@event is InputEventKey keyEvent && keyEvent.Pressed)
|
||||
{
|
||||
if (keyEvent.Keycode == Key.Escape)
|
||||
{
|
||||
OnCancelPressed();
|
||||
GetViewport().SetInputAsHandled();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
scripts/InstanceSelectionMenu.cs.uid
Normal file
1
scripts/InstanceSelectionMenu.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://clbtayqmos4qt
|
||||
@@ -7,14 +7,39 @@ namespace TheGame
|
||||
{
|
||||
private VBoxContainer _saveList;
|
||||
private SaveManager _saveManager;
|
||||
private Label _titleLabel;
|
||||
private Button _backButton;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// Lägg till i localized_ui gruppen
|
||||
AddToGroup("localized_ui");
|
||||
|
||||
_saveList = GetNode<VBoxContainer>("VBoxContainer/ScrollContainer/SaveList");
|
||||
_titleLabel = GetNode<Label>("VBoxContainer/TitleLabel");
|
||||
_backButton = GetNode<Button>("VBoxContainer/BackButton");
|
||||
_saveManager = new SaveManager();
|
||||
|
||||
UpdateLocalization();
|
||||
PopulateSaveList();
|
||||
}
|
||||
|
||||
public void UpdateLocalization()
|
||||
{
|
||||
var loc = LocalizationManager.Instance;
|
||||
_titleLabel.Text = loc.GetText("load_game_title");
|
||||
_backButton.Text = loc.GetText("load_game_back");
|
||||
}
|
||||
|
||||
public override void _Notification(int what)
|
||||
{
|
||||
if (what == NotificationWMCloseRequest)
|
||||
{
|
||||
// I load game menyn kan vi avsluta direkt
|
||||
GetTree().Quit();
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateSaveList()
|
||||
{
|
||||
// Rensa tidigare innehåll
|
||||
@@ -28,7 +53,8 @@ namespace TheGame
|
||||
if (savedGames.Count == 0)
|
||||
{
|
||||
var noSavesLabel = new Label();
|
||||
noSavesLabel.Text = "No saved games found";
|
||||
var loc = LocalizationManager.Instance;
|
||||
noSavesLabel.Text = loc.GetText("load_game_no_saves");
|
||||
noSavesLabel.HorizontalAlignment = HorizontalAlignment.Center;
|
||||
_saveList.AddChild(noSavesLabel);
|
||||
return;
|
||||
@@ -36,35 +62,69 @@ namespace TheGame
|
||||
|
||||
foreach (var saveData in savedGames)
|
||||
{
|
||||
var saveButton = new Button();
|
||||
var gameContainer = new VBoxContainer();
|
||||
gameContainer.AddThemeConstantOverride("separation", 5);
|
||||
|
||||
// Formatera tid för visning
|
||||
int hours = (int)(saveData.GameTime / 3600);
|
||||
int minutes = (int)((saveData.GameTime % 3600) / 60);
|
||||
int seconds = (int)(saveData.GameTime % 60);
|
||||
string timeText = $"{hours:D2}:{minutes:D2}:{seconds:D2}";
|
||||
// Header med seed info
|
||||
var seedLabel = new Label();
|
||||
seedLabel.Text = $"Game: {saveData.GameSeed.ToString()}";
|
||||
seedLabel.AddThemeStyleboxOverride("normal", new StyleBoxFlat());
|
||||
gameContainer.AddChild(seedLabel);
|
||||
|
||||
saveButton.Text = $"{saveData.SaveName}\nTime: {timeText}\nSaved: {saveData.SaveDate:yyyy-MM-dd HH:mm:ss}";
|
||||
// Continue button (senaste instansen)
|
||||
var latestInstance = saveData.GetLatestInstance();
|
||||
if (latestInstance != null)
|
||||
{
|
||||
var continueButton = new Button();
|
||||
continueButton.Text = $"Continue: {latestInstance.DisplayName}\nTime: {latestInstance.GetFormattedGameTime()}\nSaved: {latestInstance.SaveDate:yyyy-MM-dd HH:mm}";
|
||||
continueButton.Pressed += () => LoadGame(saveData, latestInstance);
|
||||
gameContainer.AddChild(continueButton);
|
||||
}
|
||||
|
||||
// Anslut knapp-händelse
|
||||
saveButton.Pressed += () => LoadSelectedGame(saveData);
|
||||
// Load Previous button (om det finns fler instanser)
|
||||
if (saveData.Instances.Count > 1)
|
||||
{
|
||||
var loadPreviousButton = new Button();
|
||||
loadPreviousButton.Text = $"Load Previous ({saveData.Instances.Count - 1} other saves)";
|
||||
loadPreviousButton.Pressed += () => ShowInstanceSelection(saveData);
|
||||
gameContainer.AddChild(loadPreviousButton);
|
||||
}
|
||||
|
||||
_saveList.AddChild(saveButton);
|
||||
// Separator mellan games
|
||||
var separator = new HSeparator();
|
||||
gameContainer.AddChild(separator);
|
||||
|
||||
_saveList.AddChild(gameContainer);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadSelectedGame(SaveData saveData)
|
||||
private void LoadGame(SaveData saveData, SaveInstance instance = null)
|
||||
{
|
||||
if (_saveManager.LoadGame(saveData))
|
||||
if (_saveManager.LoadGame(saveData, instance))
|
||||
{
|
||||
GetTree().ChangeSceneToFile("res://scenes/Game.tscn");
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.PrintErr("Failed to load selected game");
|
||||
|
||||
// Visa error message
|
||||
var errorDialog = new AcceptDialog();
|
||||
errorDialog.DialogText = "Failed to load the selected save.";
|
||||
errorDialog.Title = "Load Error";
|
||||
GetTree().Root.AddChild(errorDialog);
|
||||
errorDialog.PopupCentered();
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowInstanceSelection(SaveData saveData)
|
||||
{
|
||||
var instanceMenu = new InstanceSelectionMenu();
|
||||
instanceMenu.Initialize(saveData);
|
||||
GetTree().Root.AddChild(instanceMenu);
|
||||
instanceMenu.PopupCentered(new Vector2I(500, 400));
|
||||
}
|
||||
|
||||
private void _on_back_button_pressed()
|
||||
{
|
||||
GetTree().ChangeSceneToFile("res://scenes/MainMenu.tscn");
|
||||
|
||||
255
scripts/LocalizationManager.cs
Normal file
255
scripts/LocalizationManager.cs
Normal file
@@ -0,0 +1,255 @@
|
||||
using Godot;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace TheGame
|
||||
{
|
||||
public class LocalizationManager
|
||||
{
|
||||
private static LocalizationManager _instance;
|
||||
private Dictionary<string, string> _currentTranslations;
|
||||
private Dictionary<string, string> _fallbackTranslations;
|
||||
private string _currentLanguage = "eng";
|
||||
private readonly string _languagesPath;
|
||||
|
||||
public static LocalizationManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new LocalizationManager();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private LocalizationManager()
|
||||
{
|
||||
// För development: använd fil-systemet
|
||||
// För export: använd Godot's resource system
|
||||
_languagesPath = "res://resources/languages";
|
||||
|
||||
// Skapa engelska som fallback om den inte finns
|
||||
CreateDefaultEnglishFile();
|
||||
|
||||
// Ladda engelska som fallback
|
||||
_fallbackTranslations = LoadLanguageFile("eng");
|
||||
|
||||
// Ladda nuvarande språk
|
||||
_currentTranslations = _fallbackTranslations;
|
||||
}
|
||||
|
||||
private void CreateDefaultEnglishFile()
|
||||
{
|
||||
// Kontrollera om engelsk fil finns i resources
|
||||
if (!Godot.FileAccess.FileExists("res://resources/languages/eng.json"))
|
||||
{
|
||||
var defaultTranslations = new Dictionary<string, string>
|
||||
{
|
||||
// Main Menu
|
||||
{"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
|
||||
{"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 Menu
|
||||
{"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
|
||||
{"save_dialog_title", "Save Game"},
|
||||
{"save_dialog_enter_name", "Enter save name:"},
|
||||
{"save_dialog_placeholder", "My Save Game"},
|
||||
|
||||
// Settings Menu
|
||||
{"settings_title", "SETTINGS"},
|
||||
{"settings_general", "General"},
|
||||
{"settings_video", "Video"},
|
||||
{"settings_audio", "Audio"},
|
||||
{"settings_apply", "Apply"},
|
||||
{"settings_cancel", "Cancel"},
|
||||
{"settings_back", "Back"},
|
||||
|
||||
// General Settings
|
||||
{"settings_language", "Language"},
|
||||
|
||||
// Video Settings
|
||||
{"settings_fullscreen", "Fullscreen"},
|
||||
|
||||
// Audio Settings
|
||||
{"settings_master_volume", "Master Volume"},
|
||||
{"settings_background_volume", "Background Volume"},
|
||||
{"settings_effects_volume", "Effects Volume"},
|
||||
{"settings_radio_volume", "Radio Volume"},
|
||||
|
||||
// Game
|
||||
{"game_content", "GAME CONTENT HERE\\nPress ESC or click pause button to pause"},
|
||||
|
||||
// Common
|
||||
{"ok", "OK"},
|
||||
{"cancel", "Cancel"},
|
||||
{"yes", "Yes"},
|
||||
{"no", "No"}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
string jsonString = JsonSerializer.Serialize(defaultTranslations, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
});
|
||||
|
||||
// Försök spara endast i development mode
|
||||
if (OS.IsDebugBuild())
|
||||
{
|
||||
var file = Godot.FileAccess.Open("res://resources/languages/eng.json", Godot.FileAccess.ModeFlags.Write);
|
||||
if (file != null)
|
||||
{
|
||||
file.StoreString(jsonString);
|
||||
file.Close();
|
||||
GD.Print("Created default English language file in resources");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GD.PrintErr($"Failed to create English language file: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> GetAvailableLanguages()
|
||||
{
|
||||
var languages = new List<string>();
|
||||
|
||||
try
|
||||
{
|
||||
// Använd DirAccess för att läsa från resources
|
||||
var dir = DirAccess.Open("res://resources/languages");
|
||||
if (dir != null)
|
||||
{
|
||||
dir.ListDirBegin();
|
||||
string fileName = dir.GetNext();
|
||||
|
||||
while (fileName != "")
|
||||
{
|
||||
if (!dir.CurrentIsDir() && fileName.EndsWith(".json"))
|
||||
{
|
||||
string languageCode = fileName.GetBaseName();
|
||||
languages.Add(languageCode);
|
||||
}
|
||||
fileName = dir.GetNext();
|
||||
}
|
||||
dir.ListDirEnd();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GD.PrintErr($"Failed to get available languages: {ex.Message}");
|
||||
}
|
||||
|
||||
// Se till att engelska alltid finns
|
||||
if (!languages.Contains("eng"))
|
||||
{
|
||||
languages.Add("eng");
|
||||
}
|
||||
|
||||
GD.Print($"Available languages: {string.Join(", ", languages)}");
|
||||
return languages;
|
||||
}
|
||||
|
||||
private Dictionary<string, string> LoadLanguageFile(string languageCode)
|
||||
{
|
||||
var translations = new Dictionary<string, string>();
|
||||
string filePath = $"res://resources/languages/{languageCode}.json";
|
||||
|
||||
try
|
||||
{
|
||||
if (Godot.FileAccess.FileExists(filePath))
|
||||
{
|
||||
var file = Godot.FileAccess.Open(filePath, Godot.FileAccess.ModeFlags.Read);
|
||||
if (file != null)
|
||||
{
|
||||
string jsonString = file.GetAsText();
|
||||
file.Close();
|
||||
|
||||
var loadedTranslations = JsonSerializer.Deserialize<Dictionary<string, string>>(jsonString);
|
||||
if (loadedTranslations != null)
|
||||
{
|
||||
translations = loadedTranslations;
|
||||
GD.Print($"Loaded {translations.Count} translations for language: {languageCode}");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.Print($"Language file not found: {filePath}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GD.PrintErr($"Failed to load language file {filePath}: {ex.Message}");
|
||||
}
|
||||
|
||||
return translations;
|
||||
}
|
||||
|
||||
public void SetLanguage(string languageCode)
|
||||
{
|
||||
if (_currentLanguage == languageCode)
|
||||
return;
|
||||
|
||||
_currentLanguage = languageCode;
|
||||
_currentTranslations = LoadLanguageFile(languageCode);
|
||||
|
||||
GD.Print($"Language changed to: {languageCode}");
|
||||
|
||||
// Skicka signal för att uppdatera alla UI-element
|
||||
GetTree().CallGroup("localized_ui", "UpdateLocalization");
|
||||
}
|
||||
|
||||
public string GetText(string key)
|
||||
{
|
||||
// Försök hämta från nuvarande språk
|
||||
if (_currentTranslations != null && _currentTranslations.ContainsKey(key))
|
||||
{
|
||||
return _currentTranslations[key];
|
||||
}
|
||||
|
||||
// Fallback till engelska
|
||||
if (_fallbackTranslations != null && _fallbackTranslations.ContainsKey(key))
|
||||
{
|
||||
return _fallbackTranslations[key];
|
||||
}
|
||||
|
||||
// Om inte ens engelska finns, returnera nyckeln
|
||||
GD.PrintErr($"Missing translation for key: {key}");
|
||||
return key;
|
||||
}
|
||||
|
||||
public string GetCurrentLanguage()
|
||||
{
|
||||
return _currentLanguage;
|
||||
}
|
||||
|
||||
private SceneTree GetTree()
|
||||
{
|
||||
return Engine.GetMainLoop() as SceneTree;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
scripts/LocalizationManager.cs.uid
Normal file
1
scripts/LocalizationManager.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dqkp0ni5te700
|
||||
@@ -5,11 +5,37 @@ namespace TheGame
|
||||
public partial class MainMenu : Control
|
||||
{
|
||||
private Button _loadButton;
|
||||
private Label _titleLabel;
|
||||
private Button _startButton;
|
||||
private Button _settingsButton;
|
||||
private Button _exitButton;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// Lägg till i localized_ui gruppen
|
||||
AddToGroup("localized_ui");
|
||||
|
||||
_loadButton = GetNode<Button>("VBoxContainer/LoadButton");
|
||||
_titleLabel = GetNode<Label>("VBoxContainer/TitleLabel");
|
||||
_startButton = GetNode<Button>("VBoxContainer/StartButton");
|
||||
_settingsButton = GetNode<Button>("VBoxContainer/SettingsButton");
|
||||
_exitButton = GetNode<Button>("VBoxContainer/ExitButton");
|
||||
|
||||
// Initiera inställningar och språk
|
||||
GameSettings.Instance.ApplyAllSettings();
|
||||
|
||||
UpdateLoadButtonState();
|
||||
UpdateLocalization();
|
||||
}
|
||||
|
||||
public void UpdateLocalization()
|
||||
{
|
||||
var loc = LocalizationManager.Instance;
|
||||
_titleLabel.Text = loc.GetText("main_menu_title");
|
||||
_startButton.Text = loc.GetText("main_menu_start");
|
||||
_loadButton.Text = loc.GetText("main_menu_load");
|
||||
_settingsButton.Text = loc.GetText("main_menu_settings");
|
||||
_exitButton.Text = loc.GetText("main_menu_exit");
|
||||
}
|
||||
|
||||
private void UpdateLoadButtonState()
|
||||
@@ -22,14 +48,38 @@ namespace TheGame
|
||||
|
||||
private void _on_start_button_pressed()
|
||||
{
|
||||
// Rensa befintligt game state för att starta nytt spel
|
||||
GameState.ResetGameState();
|
||||
|
||||
GetTree().ChangeSceneToFile("res://scenes/Game.tscn");
|
||||
}
|
||||
|
||||
public override void _Notification(int what)
|
||||
{
|
||||
if (what == NotificationWMCloseRequest)
|
||||
{
|
||||
// I huvudmenyn kan vi avsluta direkt
|
||||
GetTree().Quit();
|
||||
}
|
||||
}
|
||||
|
||||
private void _on_load_button_pressed()
|
||||
{
|
||||
GetTree().ChangeSceneToFile("res://scenes/LoadGameMenu.tscn");
|
||||
}
|
||||
|
||||
private void _on_settings_button_pressed()
|
||||
{
|
||||
// Skapa settings menu som popup
|
||||
var settingsScene = GD.Load<PackedScene>("res://scenes/SettingsMenu.tscn");
|
||||
var settingsMenu = settingsScene.Instantiate<SettingsMenu>();
|
||||
|
||||
// Sätt process mode för main menu context
|
||||
settingsMenu.ProcessMode = ProcessModeEnum.Always;
|
||||
|
||||
GetTree().Root.AddChild(settingsMenu);
|
||||
}
|
||||
|
||||
private void _on_exit_button_pressed()
|
||||
{
|
||||
GetTree().Quit();
|
||||
|
||||
@@ -5,10 +5,39 @@ namespace TheGame
|
||||
public partial class PauseMenu : Control
|
||||
{
|
||||
private Game _gameController;
|
||||
private Label _titleLabel;
|
||||
private Button _resumeButton;
|
||||
private Button _saveButton;
|
||||
private Button _settingsButton;
|
||||
private Button _mainMenuButton;
|
||||
private Button _desktopButton;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// Lägg till i localized_ui gruppen
|
||||
AddToGroup("localized_ui");
|
||||
|
||||
_gameController = GetParent<Game>();
|
||||
|
||||
_titleLabel = GetNode<Label>("MenuContainer/TitleLabel");
|
||||
_resumeButton = GetNode<Button>("MenuContainer/ResumeButton");
|
||||
_saveButton = GetNode<Button>("MenuContainer/SaveButton");
|
||||
_settingsButton = GetNode<Button>("MenuContainer/SettingsButton");
|
||||
_mainMenuButton = GetNode<Button>("MenuContainer/MainMenuButton");
|
||||
_desktopButton = GetNode<Button>("MenuContainer/DesktopButton");
|
||||
|
||||
UpdateLocalization();
|
||||
}
|
||||
|
||||
public void UpdateLocalization()
|
||||
{
|
||||
var loc = LocalizationManager.Instance;
|
||||
_titleLabel.Text = loc.GetText("pause_menu_title");
|
||||
_resumeButton.Text = loc.GetText("pause_menu_resume");
|
||||
_saveButton.Text = loc.GetText("pause_menu_save");
|
||||
_settingsButton.Text = loc.GetText("pause_menu_settings");
|
||||
_mainMenuButton.Text = loc.GetText("pause_menu_main_menu");
|
||||
_desktopButton.Text = loc.GetText("pause_menu_desktop");
|
||||
}
|
||||
|
||||
private void _on_resume_button_pressed()
|
||||
@@ -18,23 +47,38 @@ namespace TheGame
|
||||
|
||||
private void _on_save_button_pressed()
|
||||
{
|
||||
var saveManager = new SaveManager();
|
||||
float currentTime = _gameController.GetGameTime();
|
||||
saveManager.SaveGame(currentTime);
|
||||
// Skapa och visa advanced save dialog
|
||||
var advancedSaveDialog = new AdvancedSaveDialog();
|
||||
advancedSaveDialog.ProcessMode = ProcessModeEnum.WhenPaused; // Sätt innan initialize
|
||||
advancedSaveDialog.Initialize(_gameController);
|
||||
GetTree().Root.AddChild(advancedSaveDialog);
|
||||
advancedSaveDialog.PopupCentered(new Vector2I(450, 350));
|
||||
}
|
||||
|
||||
private void _on_settings_button_pressed()
|
||||
{
|
||||
// Skapa settings menu som popup
|
||||
var settingsScene = GD.Load<PackedScene>("res://scenes/SettingsMenu.tscn");
|
||||
var settingsMenu = settingsScene.Instantiate<SettingsMenu>();
|
||||
|
||||
// Visa bekräftelse eller gå tillbaka till spelet
|
||||
_gameController.TogglePause();
|
||||
// Sätt process mode så att den fungerar när pausat
|
||||
settingsMenu.ProcessMode = ProcessModeEnum.WhenPaused;
|
||||
|
||||
GetTree().Root.AddChild(settingsMenu);
|
||||
}
|
||||
|
||||
private void _on_main_menu_button_pressed()
|
||||
{
|
||||
GetTree().Paused = false;
|
||||
GetTree().ChangeSceneToFile("res://scenes/MainMenu.tscn");
|
||||
_gameController.HandleExitRequest(() => {
|
||||
GetTree().Paused = false;
|
||||
GameState.ResetGameState();
|
||||
GetTree().ChangeSceneToFile("res://scenes/MainMenu.tscn");
|
||||
});
|
||||
}
|
||||
|
||||
private void _on_desktop_button_pressed()
|
||||
{
|
||||
GetTree().Quit();
|
||||
_gameController.HandleExitRequest(() => GetTree().Quit());
|
||||
}
|
||||
}
|
||||
}
|
||||
101
scripts/SaveDialog.cs
Normal file
101
scripts/SaveDialog.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using Godot;
|
||||
|
||||
namespace TheGame
|
||||
{
|
||||
public partial class SaveDialog : AcceptDialog
|
||||
{
|
||||
private LineEdit _nameInput;
|
||||
private Game _gameController;
|
||||
|
||||
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");
|
||||
|
||||
// Skapa innehåll för dialogen
|
||||
var vbox = new VBoxContainer();
|
||||
|
||||
var label = new Label();
|
||||
label.Text = loc.GetText("save_dialog_enter_name");
|
||||
vbox.AddChild(label);
|
||||
|
||||
_nameInput = new LineEdit();
|
||||
_nameInput.PlaceholderText = loc.GetText("save_dialog_placeholder");
|
||||
_nameInput.Text = $"Save {System.DateTime.Now:yyyy-MM-dd HH:mm}";
|
||||
vbox.AddChild(_nameInput);
|
||||
|
||||
AddChild(vbox);
|
||||
|
||||
// Sätt process mode på alla child nodes
|
||||
SetProcessModeRecursive(this, ProcessModeEnum.WhenPaused);
|
||||
|
||||
// Anslut signaler
|
||||
Confirmed += OnSaveConfirmed;
|
||||
|
||||
// Fokusera på text input
|
||||
_nameInput.GrabFocus();
|
||||
_nameInput.SelectAll();
|
||||
}
|
||||
|
||||
public void Initialize(Game gameController)
|
||||
{
|
||||
_gameController = gameController;
|
||||
}
|
||||
|
||||
private void OnSaveConfirmed()
|
||||
{
|
||||
if (_gameController != null)
|
||||
{
|
||||
string saveName = _nameInput.Text.Trim();
|
||||
if (string.IsNullOrEmpty(saveName))
|
||||
{
|
||||
saveName = $"Save {System.DateTime.Now:yyyy-MM-dd HH:mm}";
|
||||
}
|
||||
|
||||
var saveManager = new SaveManager();
|
||||
float currentTime = _gameController.GetGameTime();
|
||||
saveManager.SaveGame(currentTime, saveName);
|
||||
|
||||
// Stäng pausmenyn och återgå till spelet
|
||||
_gameController.TogglePause();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// Se till att input fungerar när spelet är pausat
|
||||
if (@event is InputEventKey keyEvent && keyEvent.Pressed)
|
||||
{
|
||||
if (keyEvent.Keycode == Key.Enter || keyEvent.Keycode == Key.KpEnter)
|
||||
{
|
||||
OnSaveConfirmed();
|
||||
GetViewport().SetInputAsHandled();
|
||||
}
|
||||
else if (keyEvent.Keycode == Key.Escape)
|
||||
{
|
||||
Hide();
|
||||
QueueFree();
|
||||
GetViewport().SetInputAsHandled();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
scripts/SaveDialog.cs.uid
Normal file
1
scripts/SaveDialog.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://fda254rf83ot
|
||||
57
scripts/SaveInstance.cs
Normal file
57
scripts/SaveInstance.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
|
||||
namespace TheGame
|
||||
{
|
||||
public class SaveInstance
|
||||
{
|
||||
public string InstanceId { get; set; }
|
||||
public float GameTime { get; set; }
|
||||
public DateTime SaveDate { get; set; }
|
||||
public string DisplayName { get; set; }
|
||||
public bool IsAutoSave { get; set; } = false;
|
||||
|
||||
public SaveInstance()
|
||||
{
|
||||
InstanceId = Guid.NewGuid().ToString("N")[..8]; // 8-tecken ID
|
||||
SaveDate = DateTime.Now;
|
||||
DisplayName = $"Save {SaveDate:HH:mm:ss}";
|
||||
}
|
||||
|
||||
public SaveInstance(float gameTime, string displayName = "", bool isAutoSave = false)
|
||||
{
|
||||
InstanceId = Guid.NewGuid().ToString("N")[..8];
|
||||
GameTime = gameTime;
|
||||
SaveDate = DateTime.Now;
|
||||
IsAutoSave = isAutoSave;
|
||||
|
||||
if (string.IsNullOrEmpty(displayName))
|
||||
{
|
||||
if (isAutoSave)
|
||||
{
|
||||
DisplayName = $"Auto-save {SaveDate:HH:mm:ss}";
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayName = $"Save {SaveDate:HH:mm:ss}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayName = displayName;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetFormattedGameTime()
|
||||
{
|
||||
int hours = (int)(GameTime / 3600);
|
||||
int minutes = (int)((GameTime % 3600) / 60);
|
||||
int seconds = (int)(GameTime % 60);
|
||||
return $"{hours:D2}:{minutes:D2}:{seconds:D2}";
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{DisplayName} - {GetFormattedGameTime()} ({SaveDate:yyyy-MM-dd HH:mm:ss})";
|
||||
}
|
||||
}
|
||||
}
|
||||
1
scripts/SaveInstance.cs.uid
Normal file
1
scripts/SaveInstance.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cxycpmfalsahg
|
||||
@@ -2,15 +2,43 @@ using Godot;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace TheGame
|
||||
{
|
||||
public class SaveData
|
||||
{
|
||||
public GameSeed GameSeed { get; set; }
|
||||
public List<SaveInstance> Instances { get; set; } = new List<SaveInstance>();
|
||||
|
||||
// Backward compatibility
|
||||
public float GameTime { get; set; }
|
||||
public DateTime SaveDate { get; set; }
|
||||
public string SaveName { get; set; }
|
||||
public string DisplayName { get; set; } = "";
|
||||
|
||||
public SaveData()
|
||||
{
|
||||
GameSeed = new GameSeed();
|
||||
}
|
||||
|
||||
public SaveData(GameSeed gameSeed)
|
||||
{
|
||||
GameSeed = gameSeed;
|
||||
}
|
||||
|
||||
public SaveInstance GetLatestInstance()
|
||||
{
|
||||
if (Instances.Count == 0) return null;
|
||||
return Instances.OrderByDescending(i => i.SaveDate).First();
|
||||
}
|
||||
|
||||
public SaveInstance GetInstanceWithHighestGameTime()
|
||||
{
|
||||
if (Instances.Count == 0) return null;
|
||||
return Instances.OrderByDescending(i => i.GameTime).First();
|
||||
}
|
||||
}
|
||||
|
||||
public class SaveManager
|
||||
@@ -19,25 +47,100 @@ namespace TheGame
|
||||
|
||||
public SaveManager()
|
||||
{
|
||||
_saveDirectory = Path.Combine(OS.GetExecutablePath().GetBaseDir(), "saves");
|
||||
// Använd user data directory istället för executable directory
|
||||
string userDataDir = OS.GetUserDataDir();
|
||||
_saveDirectory = Path.Combine(userDataDir, "saves");
|
||||
|
||||
// Skapa saves mapp om den inte finns
|
||||
if (!Directory.Exists(_saveDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(_saveDirectory);
|
||||
GD.Print($"Created saves directory: {_saveDirectory}");
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.Print($"Using saves directory: {_saveDirectory}");
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveGame(float gameTime)
|
||||
public bool SaveGame(float gameTime, string displayName = "", GameSeed gameSeed = null, bool overwriteLatest = true)
|
||||
{
|
||||
var saveData = new SaveData
|
||||
try
|
||||
{
|
||||
GameTime = gameTime,
|
||||
SaveDate = DateTime.Now,
|
||||
SaveName = $"Save_{DateTime.Now:yyyyMMdd_HHmmss}"
|
||||
};
|
||||
// Om ingen seed angiven, använd nuvarande eller skapa ny
|
||||
if (gameSeed == null)
|
||||
{
|
||||
gameSeed = GameState.CurrentGameSeed ?? new GameSeed();
|
||||
GameState.CurrentGameSeed = gameSeed;
|
||||
}
|
||||
|
||||
// Sök efter befintlig SaveData för denna seed
|
||||
var existingSaveData = FindSaveDataBySeed(gameSeed);
|
||||
|
||||
if (existingSaveData == null)
|
||||
{
|
||||
// Skapa ny SaveData för denna seed
|
||||
existingSaveData = new SaveData(gameSeed);
|
||||
}
|
||||
|
||||
// Skapa ny instans
|
||||
var newInstance = new SaveInstance(gameTime, displayName);
|
||||
|
||||
if (overwriteLatest && existingSaveData.Instances.Count > 0)
|
||||
{
|
||||
// Skriv över den senaste instansen
|
||||
var latestInstance = existingSaveData.GetLatestInstance();
|
||||
existingSaveData.Instances.Remove(latestInstance);
|
||||
}
|
||||
|
||||
existingSaveData.Instances.Add(newInstance);
|
||||
|
||||
// Spara till fil
|
||||
bool saveSuccess = SaveSeedToFile(existingSaveData);
|
||||
|
||||
if (saveSuccess)
|
||||
{
|
||||
GD.Print($"Game saved - Seed: {gameSeed.SeedValue}, Instance: {newInstance.DisplayName}");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.PrintErr($"Failed to save game - Seed: {gameSeed.SeedValue}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GD.PrintErr($"Error saving game: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public SaveData FindSaveDataBySeed(GameSeed gameSeed)
|
||||
{
|
||||
string fileName = $"seed_{gameSeed.SeedValue}.json";
|
||||
string filePath = Path.Combine(_saveDirectory, fileName);
|
||||
|
||||
string fileName = $"{saveData.SaveName}.json";
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
string jsonString = File.ReadAllText(filePath);
|
||||
var saveData = JsonSerializer.Deserialize<SaveData>(jsonString);
|
||||
return saveData;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GD.PrintErr($"Failed to load seed file {filePath}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool SaveSeedToFile(SaveData saveData)
|
||||
{
|
||||
string fileName = $"seed_{saveData.GameSeed.SeedValue}.json";
|
||||
string filePath = Path.Combine(_saveDirectory, fileName);
|
||||
|
||||
try
|
||||
@@ -47,11 +150,13 @@ namespace TheGame
|
||||
WriteIndented = true
|
||||
});
|
||||
File.WriteAllText(filePath, jsonString);
|
||||
GD.Print($"Game saved successfully: {fileName}");
|
||||
GD.Print($"Seed saved to: {filePath}");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GD.PrintErr($"Failed to save game: {ex.Message}");
|
||||
GD.PrintErr($"Failed to save seed file: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,22 +164,50 @@ namespace TheGame
|
||||
{
|
||||
var savedGames = new List<SaveData>();
|
||||
|
||||
GD.Print($"Looking for saves in: {_saveDirectory}");
|
||||
|
||||
if (!Directory.Exists(_saveDirectory))
|
||||
{
|
||||
GD.Print("Save directory does not exist");
|
||||
return savedGames;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var saveFiles = Directory.GetFiles(_saveDirectory, "*.json");
|
||||
// Ladda både nya seed-filer och gamla format för backward compatibility
|
||||
var allFiles = Directory.GetFiles(_saveDirectory, "*.json");
|
||||
GD.Print($"Found {allFiles.Length} save files");
|
||||
|
||||
foreach (string filePath in saveFiles)
|
||||
foreach (string filePath in allFiles)
|
||||
{
|
||||
GD.Print($"Processing save file: {filePath}");
|
||||
try
|
||||
{
|
||||
string jsonString = File.ReadAllText(filePath);
|
||||
var saveData = JsonSerializer.Deserialize<SaveData>(jsonString);
|
||||
if (saveData != null)
|
||||
|
||||
if (Path.GetFileName(filePath).StartsWith("seed_"))
|
||||
{
|
||||
savedGames.Add(saveData);
|
||||
// Ny seed-baserad fil
|
||||
var saveData = JsonSerializer.Deserialize<SaveData>(jsonString);
|
||||
if (saveData != null && saveData.Instances.Count > 0)
|
||||
{
|
||||
savedGames.Add(saveData);
|
||||
GD.Print($"Successfully loaded seed: {saveData.GameSeed.SeedValue} with {saveData.Instances.Count} instances");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Gammal fil - konvertera till nytt format
|
||||
var oldSaveData = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonString);
|
||||
if (oldSaveData != null)
|
||||
{
|
||||
var convertedSave = ConvertOldSaveFormat(oldSaveData);
|
||||
if (convertedSave != null)
|
||||
{
|
||||
savedGames.Add(convertedSave);
|
||||
GD.Print($"Converted old save format");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -83,8 +216,9 @@ namespace TheGame
|
||||
}
|
||||
}
|
||||
|
||||
// Sortera efter datum (nyast först)
|
||||
savedGames.Sort((a, b) => b.SaveDate.CompareTo(a.SaveDate));
|
||||
// Sortera efter senaste aktivitet
|
||||
savedGames = savedGames.OrderByDescending(s => s.GetLatestInstance()?.SaveDate ?? DateTime.MinValue).ToList();
|
||||
GD.Print($"Total loaded saves: {savedGames.Count}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -94,11 +228,56 @@ namespace TheGame
|
||||
return savedGames;
|
||||
}
|
||||
|
||||
public bool LoadGame(SaveData saveData)
|
||||
private SaveData ConvertOldSaveFormat(Dictionary<string, object> oldData)
|
||||
{
|
||||
try
|
||||
{
|
||||
GameState.LoadedGameTime = saveData.GameTime;
|
||||
// Extrahera värden från gammal format
|
||||
var gameTime = Convert.ToSingle(oldData.GetValueOrDefault("GameTime", 0f));
|
||||
var saveDateStr = oldData.GetValueOrDefault("SaveDate", "")?.ToString();
|
||||
var displayName = oldData.GetValueOrDefault("DisplayName", "")?.ToString();
|
||||
|
||||
if (DateTime.TryParse(saveDateStr, out DateTime saveDate))
|
||||
{
|
||||
var gameSeed = new GameSeed($"OLD_{DateTime.Now.Ticks % 100000000:X}", "Converted Save");
|
||||
var saveData = new SaveData(gameSeed);
|
||||
|
||||
var instance = new SaveInstance(gameTime, displayName ?? "Converted Save");
|
||||
instance.SaveDate = saveDate;
|
||||
saveData.Instances.Add(instance);
|
||||
|
||||
return saveData;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GD.PrintErr($"Failed to convert old save format: {ex.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool LoadGame(SaveData saveData, SaveInstance instance = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Om ingen specifik instans angiven, ta den med högst speltid
|
||||
if (instance == null)
|
||||
{
|
||||
instance = saveData.GetInstanceWithHighestGameTime();
|
||||
}
|
||||
|
||||
if (instance == null)
|
||||
{
|
||||
GD.PrintErr("No valid instance found to load");
|
||||
return false;
|
||||
}
|
||||
|
||||
GameState.LoadedGameTime = instance.GameTime;
|
||||
GameState.CurrentGameSeed = saveData.GameSeed;
|
||||
GameState.LoadedInstance = instance;
|
||||
|
||||
GD.Print($"Loaded game - Seed: {saveData.GameSeed.SeedValue}, Instance: {instance.DisplayName}, Time: {instance.GetFormattedGameTime()}");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
288
scripts/SettingsMenu.cs
Normal file
288
scripts/SettingsMenu.cs
Normal file
@@ -0,0 +1,288 @@
|
||||
using Godot;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TheGame
|
||||
{
|
||||
public partial class SettingsMenu : Control
|
||||
{
|
||||
// UI References
|
||||
private Label _titleLabel;
|
||||
private TabContainer _tabContainer;
|
||||
|
||||
// General Tab
|
||||
private Label _languageLabel;
|
||||
private OptionButton _languageOption;
|
||||
|
||||
// Video Tab
|
||||
private Label _fullscreenLabel;
|
||||
private CheckBox _fullscreenCheck;
|
||||
|
||||
// Audio Tab
|
||||
private Label _masterLabel;
|
||||
private HSlider _masterSlider;
|
||||
private Label _backgroundLabel;
|
||||
private HSlider _backgroundSlider;
|
||||
private Label _effectsLabel;
|
||||
private HSlider _effectsSlider;
|
||||
private Label _radioLabel;
|
||||
private HSlider _radioSlider;
|
||||
|
||||
// Buttons
|
||||
private Button _applyButton;
|
||||
private Button _cancelButton;
|
||||
private Button _backButton;
|
||||
|
||||
// Settings
|
||||
private GameSettings _settings;
|
||||
private GameSettings _originalSettings;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// Lägg till i localized_ui gruppen för språkuppdateringar
|
||||
AddToGroup("localized_ui");
|
||||
|
||||
// Sätt process mode baserat på om spelet är pausat eller inte
|
||||
if (GetTree().Paused)
|
||||
{
|
||||
ProcessMode = ProcessModeEnum.WhenPaused;
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessMode = ProcessModeEnum.Always;
|
||||
}
|
||||
|
||||
// Hämta UI referenser
|
||||
GetUIReferences();
|
||||
|
||||
// Ladda nuvarande inställningar
|
||||
_settings = GameSettings.Instance;
|
||||
_originalSettings = new GameSettings
|
||||
{
|
||||
Fullscreen = _settings.Fullscreen,
|
||||
MasterVolume = _settings.MasterVolume,
|
||||
BackgroundVolume = _settings.BackgroundVolume,
|
||||
EffectsVolume = _settings.EffectsVolume,
|
||||
RadioVolume = _settings.RadioVolume,
|
||||
Language = _settings.Language
|
||||
};
|
||||
|
||||
// Sätt process mode på alla UI element så de fungerar när pausat
|
||||
SetProcessModeRecursive(this, ProcessModeEnum.WhenPaused);
|
||||
|
||||
// Initiera UI
|
||||
InitializeLanguageOptions();
|
||||
LoadCurrentSettings();
|
||||
UpdateLocalization();
|
||||
}
|
||||
|
||||
public override void _Notification(int what)
|
||||
{
|
||||
if (what == NotificationWMCloseRequest)
|
||||
{
|
||||
// Settings menyn är en popup, så vi stänger bara den själv
|
||||
_on_cancel_button_pressed();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetProcessModeRecursive(Node node, ProcessModeEnum mode)
|
||||
{
|
||||
if (node is Control control)
|
||||
{
|
||||
control.ProcessMode = mode;
|
||||
}
|
||||
|
||||
foreach (Node child in node.GetChildren())
|
||||
{
|
||||
SetProcessModeRecursive(child, mode);
|
||||
}
|
||||
}
|
||||
|
||||
private void GetUIReferences()
|
||||
{
|
||||
_titleLabel = GetNode<Label>("MainContainer/TitleLabel");
|
||||
_tabContainer = GetNode<TabContainer>("MainContainer/TabContainer");
|
||||
|
||||
// General
|
||||
_languageLabel = GetNode<Label>("MainContainer/TabContainer/General/GeneralVBox/LanguageContainer/LanguageLabel");
|
||||
_languageOption = GetNode<OptionButton>("MainContainer/TabContainer/General/GeneralVBox/LanguageContainer/LanguageOption");
|
||||
|
||||
// Video
|
||||
_fullscreenLabel = GetNode<Label>("MainContainer/TabContainer/Video/VideoVBox/FullscreenContainer/FullscreenLabel");
|
||||
_fullscreenCheck = GetNode<CheckBox>("MainContainer/TabContainer/Video/VideoVBox/FullscreenContainer/FullscreenCheck");
|
||||
|
||||
// Audio
|
||||
_masterLabel = GetNode<Label>("MainContainer/TabContainer/Audio/AudioVBox/MasterContainer/MasterLabel");
|
||||
_masterSlider = GetNode<HSlider>("MainContainer/TabContainer/Audio/AudioVBox/MasterContainer/MasterSlider");
|
||||
_backgroundLabel = GetNode<Label>("MainContainer/TabContainer/Audio/AudioVBox/BackgroundContainer/BackgroundLabel");
|
||||
_backgroundSlider = GetNode<HSlider>("MainContainer/TabContainer/Audio/AudioVBox/BackgroundContainer/BackgroundSlider");
|
||||
_effectsLabel = GetNode<Label>("MainContainer/TabContainer/Audio/AudioVBox/EffectsContainer/EffectsLabel");
|
||||
_effectsSlider = GetNode<HSlider>("MainContainer/TabContainer/Audio/AudioVBox/EffectsContainer/EffectsSlider");
|
||||
_radioLabel = GetNode<Label>("MainContainer/TabContainer/Audio/AudioVBox/RadioContainer/RadioLabel");
|
||||
_radioSlider = GetNode<HSlider>("MainContainer/TabContainer/Audio/AudioVBox/RadioContainer/RadioSlider");
|
||||
|
||||
// Buttons
|
||||
_applyButton = GetNode<Button>("MainContainer/ButtonContainer/ApplyButton");
|
||||
_cancelButton = GetNode<Button>("MainContainer/ButtonContainer/CancelButton");
|
||||
_backButton = GetNode<Button>("MainContainer/ButtonContainer/BackButton");
|
||||
}
|
||||
|
||||
private void InitializeLanguageOptions()
|
||||
{
|
||||
_languageOption.Clear();
|
||||
|
||||
var availableLanguages = LocalizationManager.Instance.GetAvailableLanguages();
|
||||
var languageNames = new Dictionary<string, string>
|
||||
{
|
||||
{"eng", "English"},
|
||||
{"sve", "Svenska"}
|
||||
};
|
||||
|
||||
foreach (string lang in availableLanguages)
|
||||
{
|
||||
string displayName = languageNames.ContainsKey(lang) ? languageNames[lang] : lang.ToUpper();
|
||||
_languageOption.AddItem(displayName);
|
||||
_languageOption.SetItemMetadata(_languageOption.GetItemCount() - 1, lang);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadCurrentSettings()
|
||||
{
|
||||
// Language
|
||||
for (int i = 0; i < _languageOption.GetItemCount(); i++)
|
||||
{
|
||||
if (_languageOption.GetItemMetadata(i).AsString() == _settings.Language)
|
||||
{
|
||||
_languageOption.Selected = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Video
|
||||
_fullscreenCheck.ButtonPressed = _settings.Fullscreen;
|
||||
|
||||
// Audio
|
||||
_masterSlider.Value = _settings.MasterVolume;
|
||||
_backgroundSlider.Value = _settings.BackgroundVolume;
|
||||
_effectsSlider.Value = _settings.EffectsVolume;
|
||||
_radioSlider.Value = _settings.RadioVolume;
|
||||
|
||||
UpdateVolumeLabels();
|
||||
}
|
||||
|
||||
private void UpdateVolumeLabels()
|
||||
{
|
||||
var loc = LocalizationManager.Instance;
|
||||
_masterLabel.Text = $"{loc.GetText("settings_master_volume")}: {(int)_masterSlider.Value}";
|
||||
_backgroundLabel.Text = $"{loc.GetText("settings_background_volume")}: {(int)_backgroundSlider.Value}";
|
||||
_effectsLabel.Text = $"{loc.GetText("settings_effects_volume")}: {(int)_effectsSlider.Value}";
|
||||
_radioLabel.Text = $"{loc.GetText("settings_radio_volume")}: {(int)_radioSlider.Value}";
|
||||
}
|
||||
|
||||
public void UpdateLocalization()
|
||||
{
|
||||
var loc = LocalizationManager.Instance;
|
||||
|
||||
_titleLabel.Text = loc.GetText("settings_title");
|
||||
|
||||
// Tab names
|
||||
_tabContainer.SetTabTitle(0, loc.GetText("settings_general"));
|
||||
_tabContainer.SetTabTitle(1, loc.GetText("settings_video"));
|
||||
_tabContainer.SetTabTitle(2, loc.GetText("settings_audio"));
|
||||
|
||||
// General
|
||||
_languageLabel.Text = loc.GetText("settings_language") + ":";
|
||||
|
||||
// Video
|
||||
_fullscreenLabel.Text = loc.GetText("settings_fullscreen") + ":";
|
||||
|
||||
// Audio
|
||||
UpdateVolumeLabels();
|
||||
|
||||
// Buttons
|
||||
_applyButton.Text = loc.GetText("settings_apply");
|
||||
_cancelButton.Text = loc.GetText("settings_cancel");
|
||||
_backButton.Text = loc.GetText("settings_back");
|
||||
}
|
||||
|
||||
// Signal handlers
|
||||
private void _on_language_option_item_selected(int index)
|
||||
{
|
||||
string selectedLanguage = _languageOption.GetItemMetadata(index).AsString();
|
||||
_settings.Language = selectedLanguage;
|
||||
|
||||
// Tillämpa språkändring direkt
|
||||
LocalizationManager.Instance.SetLanguage(selectedLanguage);
|
||||
}
|
||||
|
||||
private void _on_fullscreen_check_toggled(bool buttonPressed)
|
||||
{
|
||||
_settings.Fullscreen = buttonPressed;
|
||||
}
|
||||
|
||||
private void _on_master_slider_value_changed(double value)
|
||||
{
|
||||
_settings.MasterVolume = (int)value;
|
||||
UpdateVolumeLabels();
|
||||
}
|
||||
|
||||
private void _on_background_slider_value_changed(double value)
|
||||
{
|
||||
_settings.BackgroundVolume = (int)value;
|
||||
UpdateVolumeLabels();
|
||||
}
|
||||
|
||||
private void _on_effects_slider_value_changed(double value)
|
||||
{
|
||||
_settings.EffectsVolume = (int)value;
|
||||
UpdateVolumeLabels();
|
||||
}
|
||||
|
||||
private void _on_radio_slider_value_changed(double value)
|
||||
{
|
||||
_settings.RadioVolume = (int)value;
|
||||
UpdateVolumeLabels();
|
||||
}
|
||||
|
||||
private void _on_apply_button_pressed()
|
||||
{
|
||||
_settings.ApplyAllSettings();
|
||||
GoBack();
|
||||
}
|
||||
|
||||
private void _on_cancel_button_pressed()
|
||||
{
|
||||
// Återställ inställningar
|
||||
_settings.Fullscreen = _originalSettings.Fullscreen;
|
||||
_settings.MasterVolume = _originalSettings.MasterVolume;
|
||||
_settings.BackgroundVolume = _originalSettings.BackgroundVolume;
|
||||
_settings.EffectsVolume = _originalSettings.EffectsVolume;
|
||||
_settings.RadioVolume = _originalSettings.RadioVolume;
|
||||
_settings.Language = _originalSettings.Language;
|
||||
|
||||
// Återställ språk
|
||||
LocalizationManager.Instance.SetLanguage(_originalSettings.Language);
|
||||
|
||||
GoBack();
|
||||
}
|
||||
|
||||
private void _on_back_button_pressed()
|
||||
{
|
||||
_on_apply_button_pressed(); // Spara ändringar när man går tillbaka
|
||||
}
|
||||
|
||||
private void GoBack()
|
||||
{
|
||||
// Kontrollera varifrån vi kom
|
||||
if (GetTree().Paused)
|
||||
{
|
||||
// Vi kom från pausmenyn - ta bara bort settings window
|
||||
QueueFree();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Vi kom från huvudmenyn
|
||||
GetTree().ChangeSceneToFile("res://scenes/MainMenu.tscn");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
scripts/SettingsMenu.cs.uid
Normal file
1
scripts/SettingsMenu.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://di6gyor6n6lje
|
||||
Reference in New Issue
Block a user