feature/match3/move-gems (#7)

Reviewed-on: #7
Co-authored-by: Vladimir nett00n Budylnikov <git@nett00n.org>
Co-committed-by: Vladimir nett00n Budylnikov <git@nett00n.org>
This commit is contained in:
2025-09-25 11:48:08 +02:00
committed by nett00n
parent e76297b3f3
commit ea8c85d7ad
53 changed files with 2335 additions and 524 deletions

View File

@@ -0,0 +1 @@
uid://drnyggsvb7d84

View File

@@ -0,0 +1 @@
uid://0dlmqub0opy7

View File

@@ -4,6 +4,7 @@ const GAME_SCENE_PATH := "res://scenes/game/game.tscn"
const MAIN_SCENE_PATH := "res://scenes/main/main.tscn"
var pending_gameplay_mode: String = "match3"
var is_changing_scene: bool = false
func start_new_game() -> void:
start_game_with_mode("match3")
@@ -15,25 +16,86 @@ func start_clickomania_game() -> void:
start_game_with_mode("clickomania")
func start_game_with_mode(gameplay_mode: String) -> void:
# Input validation for gameplay mode
if not gameplay_mode or gameplay_mode.is_empty():
DebugManager.log_error("Empty or null gameplay mode provided", "GameManager")
return
if not gameplay_mode is String:
DebugManager.log_error("Invalid gameplay mode type: " + str(typeof(gameplay_mode)), "GameManager")
return
# Prevent concurrent scene changes
if is_changing_scene:
DebugManager.log_warn("Scene change already in progress, ignoring request", "GameManager")
return
# Validate gameplay mode against allowed values
var valid_modes = ["match3", "clickomania"]
if not gameplay_mode in valid_modes:
DebugManager.log_error("Invalid gameplay mode: '%s'. Valid modes: %s" % [gameplay_mode, str(valid_modes)], "GameManager")
return
is_changing_scene = true
pending_gameplay_mode = gameplay_mode
var packed_scene := load(GAME_SCENE_PATH)
if not packed_scene or not packed_scene is PackedScene:
DebugManager.log_error("Failed to load Game scene at: %s" % GAME_SCENE_PATH, "GameManager")
is_changing_scene = false
return
get_tree().change_scene_to_packed(packed_scene)
# Wait one frame for the scene to be ready, then set gameplay mode
var result = get_tree().change_scene_to_packed(packed_scene)
if result != OK:
DebugManager.log_error("Failed to change to game scene (Error code: %d)" % result, "GameManager")
is_changing_scene = false
return
# Wait for scene to be properly instantiated and added to tree
await get_tree().process_frame
if get_tree().current_scene and get_tree().current_scene.has_method("set_gameplay_mode"):
await get_tree().process_frame # Additional frame for complete initialization
# Validate scene was loaded successfully
if not get_tree().current_scene:
DebugManager.log_error("Current scene is null after scene change", "GameManager")
is_changing_scene = false
return
# Set gameplay mode with timeout protection
if get_tree().current_scene.has_method("set_gameplay_mode"):
DebugManager.log_info("Setting gameplay mode to: %s" % pending_gameplay_mode, "GameManager")
get_tree().current_scene.set_gameplay_mode(pending_gameplay_mode)
else:
DebugManager.log_error("Game scene does not have set_gameplay_mode method", "GameManager")
is_changing_scene = false
func save_game() -> void:
DebugManager.log_info("Game saved (mock)", "GameManager")
func exit_to_main_menu() -> void:
# Prevent concurrent scene changes
if is_changing_scene:
DebugManager.log_warn("Scene change already in progress, ignoring exit to main menu request", "GameManager")
return
is_changing_scene = true
DebugManager.log_info("Attempting to exit to main menu", "GameManager")
var packed_scene := load(MAIN_SCENE_PATH)
if not packed_scene or not packed_scene is PackedScene:
DebugManager.log_error("Failed to load Main scene at: %s" % MAIN_SCENE_PATH, "GameManager")
is_changing_scene = false
return
DebugManager.log_info("Loading main scene", "GameManager")
get_tree().change_scene_to_packed(packed_scene)
var result = get_tree().change_scene_to_packed(packed_scene)
if result != OK:
DebugManager.log_error("Failed to change to main scene (Error code: %d)" % result, "GameManager")
is_changing_scene = false
return
DebugManager.log_info("Successfully loaded main scene", "GameManager")
# Wait for scene to be ready, then mark scene change as complete
await get_tree().process_frame
is_changing_scene = false

View File

@@ -0,0 +1 @@
uid://f872hh65svdh

View File

@@ -0,0 +1 @@
uid://canwj4bnuyq2l

View File

@@ -24,61 +24,162 @@ func _ready():
func load_settings():
var config = ConfigFile.new()
if config.load(SETTINGS_FILE) == OK:
var load_result = config.load(SETTINGS_FILE)
# Ensure settings dictionary exists
if settings.is_empty():
settings = default_settings.duplicate()
if load_result == OK:
for key in default_settings.keys():
settings[key] = config.get_value("settings", key, default_settings[key])
var loaded_value = config.get_value("settings", key, default_settings[key])
# Validate loaded settings before applying
if _validate_setting_value(key, loaded_value):
settings[key] = loaded_value
else:
DebugManager.log_warn("Invalid setting value for '%s', using default: %s" % [key, str(default_settings[key])], "SettingsManager")
settings[key] = default_settings[key]
DebugManager.log_info("Settings loaded: " + str(settings), "SettingsManager")
else:
DebugManager.log_warn("No settings file found, using defaults", "SettingsManager")
DebugManager.log_info("Language is set to: " + str(settings["language"]), "SettingsManager")
TranslationServer.set_locale(settings["language"])
AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Master"), linear_to_db(settings["master_volume"]))
AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Music"), linear_to_db(settings["music_volume"]))
AudioServer.set_bus_volume_db(AudioServer.get_bus_index("SFX"), linear_to_db(settings["sfx_volume"]))
DebugManager.log_warn("No settings file found (Error code: %d), using defaults" % load_result, "SettingsManager")
settings = default_settings.duplicate()
# Apply settings with error handling
_apply_all_settings()
func _apply_all_settings():
DebugManager.log_info("Applying settings: " + str(settings), "SettingsManager")
# Apply language setting
if "language" in settings:
TranslationServer.set_locale(settings["language"])
# Apply audio settings with error checking
var master_bus = AudioServer.get_bus_index("Master")
var music_bus = AudioServer.get_bus_index("Music")
var sfx_bus = AudioServer.get_bus_index("SFX")
if master_bus >= 0 and "master_volume" in settings:
AudioServer.set_bus_volume_db(master_bus, linear_to_db(settings["master_volume"]))
else:
DebugManager.log_warn("Master audio bus not found or master_volume setting missing", "SettingsManager")
if music_bus >= 0 and "music_volume" in settings:
AudioServer.set_bus_volume_db(music_bus, linear_to_db(settings["music_volume"]))
else:
DebugManager.log_warn("Music audio bus not found or music_volume setting missing", "SettingsManager")
if sfx_bus >= 0 and "sfx_volume" in settings:
AudioServer.set_bus_volume_db(sfx_bus, linear_to_db(settings["sfx_volume"]))
else:
DebugManager.log_warn("SFX audio bus not found or sfx_volume setting missing", "SettingsManager")
func save_settings():
var config = ConfigFile.new()
for key in settings.keys():
config.set_value("settings", key, settings[key])
config.save(SETTINGS_FILE)
var save_result = config.save(SETTINGS_FILE)
if save_result != OK:
DebugManager.log_error("Failed to save settings (Error code: %d)" % save_result, "SettingsManager")
return false
DebugManager.log_info("Settings saved: " + str(settings), "SettingsManager")
return true
func get_setting(key: String):
return settings.get(key)
func set_setting(key: String, value):
func set_setting(key: String, value) -> bool:
if not key in default_settings:
DebugManager.log_error("Unknown setting key: " + key, "SettingsManager")
return false
# Validate value type and range based on key
if not _validate_setting_value(key, value):
DebugManager.log_error("Invalid value for setting '%s': %s" % [key, str(value)], "SettingsManager")
return false
settings[key] = value
_apply_setting_side_effect(key, value)
return true
func _validate_setting_value(key: String, value) -> bool:
match key:
"master_volume", "music_volume", "sfx_volume":
return value is float and value >= 0.0 and value <= 1.0
"language":
if not value is String:
return false
# Check if language is supported
if languages_data.has("languages"):
return value in languages_data.languages
else:
# Fallback to basic validation if languages not loaded
return value in ["en", "ru"]
# Default validation: accept if type matches default setting type
var default_value = default_settings.get(key)
return typeof(value) == typeof(default_value)
func _apply_setting_side_effect(key: String, value) -> void:
match key:
"language":
TranslationServer.set_locale(value)
"master_volume":
AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Master"), linear_to_db(value))
if AudioServer.get_bus_index("Master") >= 0:
AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Master"), linear_to_db(value))
"music_volume":
AudioManager.update_music_volume(value)
"sfx_volume":
AudioServer.set_bus_volume_db(AudioServer.get_bus_index("SFX"), linear_to_db(value))
if AudioServer.get_bus_index("SFX") >= 0:
AudioServer.set_bus_volume_db(AudioServer.get_bus_index("SFX"), linear_to_db(value))
func load_languages():
var file = FileAccess.open(LANGUAGES_JSON_PATH, FileAccess.READ)
if not file:
DebugManager.log_error("Could not open languages.json", "SettingsManager")
var error_code = FileAccess.get_open_error()
DebugManager.log_error("Could not open languages.json (Error code: %d)" % error_code, "SettingsManager")
_load_default_languages()
return
var json_string = file.get_as_text()
var file_error = file.get_error()
file.close()
if file_error != OK:
DebugManager.log_error("Error reading languages.json (Error code: %d)" % file_error, "SettingsManager")
_load_default_languages()
return
var json = JSON.new()
if json.parse(json_string) != OK:
DebugManager.log_error("Error parsing languages.json", "SettingsManager")
var parse_result = json.parse(json_string)
if parse_result != OK:
DebugManager.log_error("JSON parsing failed at line %d: %s" % [json.error_line, json.error_string], "SettingsManager")
_load_default_languages()
return
if not json.data or not json.data is Dictionary:
DebugManager.log_error("Invalid JSON data structure in languages.json", "SettingsManager")
_load_default_languages()
return
languages_data = json.data
if languages_data.has("languages"):
if languages_data.has("languages") and languages_data.languages is Dictionary:
DebugManager.log_info("Languages loaded: " + str(languages_data.languages.keys()), "SettingsManager")
else:
DebugManager.log_warn("Languages.json missing 'languages' dictionary, using defaults", "SettingsManager")
_load_default_languages()
func _load_default_languages():
# Fallback language data when JSON file fails to load
languages_data = {
"languages": {
"en": {"name": "English", "flag": "🇺🇸"},
"ru": {"name": "Русский", "flag": "🇷🇺"}
}
}
DebugManager.log_info("Default languages loaded as fallback", "SettingsManager")
func get_languages_data():
return languages_data

View File

@@ -0,0 +1 @@
uid://b6xjr0i5puukp

View File

@@ -0,0 +1 @@
uid://5rofo0bdpqee