more lint and formatting
Some checks failed
Some checks failed
This commit is contained in:
@@ -22,7 +22,9 @@ func _ready():
|
||||
|
||||
var orig_stream = _load_stream()
|
||||
if not orig_stream:
|
||||
DebugManager.log_error("Failed to load music stream: %s" % MUSIC_PATH, "AudioManager")
|
||||
DebugManager.log_error(
|
||||
"Failed to load music stream: %s" % MUSIC_PATH, "AudioManager"
|
||||
)
|
||||
return
|
||||
|
||||
var stream = orig_stream.duplicate(true) as AudioStream
|
||||
@@ -52,7 +54,9 @@ func _configure_stream_loop(stream: AudioStream) -> void:
|
||||
|
||||
func _configure_audio_bus() -> void:
|
||||
music_player.bus = "Music"
|
||||
music_player.volume_db = linear_to_db(SettingsManager.get_setting("music_volume"))
|
||||
music_player.volume_db = linear_to_db(
|
||||
SettingsManager.get_setting("music_volume")
|
||||
)
|
||||
|
||||
|
||||
func update_music_volume(volume: float) -> void:
|
||||
|
||||
@@ -83,7 +83,9 @@ func _log_level_to_string(level: LogLevel) -> String:
|
||||
return level_strings.get(level, "UNKNOWN")
|
||||
|
||||
|
||||
func _format_log_message(level: LogLevel, message: String, category: String = "") -> String:
|
||||
func _format_log_message(
|
||||
level: LogLevel, message: String, category: String = ""
|
||||
) -> String:
|
||||
"""Format log message with timestamp, level, category, and content"""
|
||||
var timestamp = Time.get_datetime_string_from_system()
|
||||
var level_str = _log_level_to_string(level)
|
||||
|
||||
@@ -49,14 +49,17 @@ func start_game_with_mode(gameplay_mode: String) -> void:
|
||||
|
||||
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")
|
||||
DebugManager.log_error(
|
||||
"Failed to load Game scene at: %s" % GAME_SCENE_PATH, "GameManager"
|
||||
)
|
||||
is_changing_scene = false
|
||||
return
|
||||
|
||||
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"
|
||||
"Failed to change to game scene (Error code: %d)" % result,
|
||||
"GameManager"
|
||||
)
|
||||
is_changing_scene = false
|
||||
return
|
||||
@@ -67,22 +70,31 @@ func start_game_with_mode(gameplay_mode: String) -> void:
|
||||
|
||||
# Validate scene was loaded successfully
|
||||
if not get_tree().current_scene:
|
||||
DebugManager.log_error("Current scene is null after scene change", "GameManager")
|
||||
DebugManager.log_error(
|
||||
"Current scene is null after scene change", "GameManager"
|
||||
)
|
||||
is_changing_scene = false
|
||||
return
|
||||
|
||||
# Configure game scene with requested gameplay mode
|
||||
if get_tree().current_scene.has_method("set_gameplay_mode"):
|
||||
DebugManager.log_info("Setting gameplay mode to: %s" % pending_gameplay_mode, "GameManager")
|
||||
DebugManager.log_info(
|
||||
"Setting gameplay mode to: %s" % pending_gameplay_mode,
|
||||
"GameManager"
|
||||
)
|
||||
get_tree().current_scene.set_gameplay_mode(pending_gameplay_mode)
|
||||
|
||||
# Load saved score
|
||||
if get_tree().current_scene.has_method("set_global_score"):
|
||||
var saved_score = SaveManager.get_current_score()
|
||||
DebugManager.log_info("Loading saved score: %d" % saved_score, "GameManager")
|
||||
DebugManager.log_info(
|
||||
"Loading saved score: %d" % saved_score, "GameManager"
|
||||
)
|
||||
get_tree().current_scene.set_global_score(saved_score)
|
||||
else:
|
||||
DebugManager.log_error("Game scene does not have set_gameplay_mode method", "GameManager")
|
||||
DebugManager.log_error(
|
||||
"Game scene does not have set_gameplay_mode method", "GameManager"
|
||||
)
|
||||
|
||||
is_changing_scene = false
|
||||
|
||||
@@ -91,11 +103,16 @@ func save_game() -> void:
|
||||
"""Save current game state and score via SaveManager"""
|
||||
# Get current score from the active game scene
|
||||
var current_score = 0
|
||||
if get_tree().current_scene and get_tree().current_scene.has_method("get_global_score"):
|
||||
if (
|
||||
get_tree().current_scene
|
||||
and get_tree().current_scene.has_method("get_global_score")
|
||||
):
|
||||
current_score = get_tree().current_scene.get_global_score()
|
||||
|
||||
SaveManager.finish_game(current_score)
|
||||
DebugManager.log_info("Game saved with score: %d" % current_score, "GameManager")
|
||||
DebugManager.log_info(
|
||||
"Game saved with score: %d" % current_score, "GameManager"
|
||||
)
|
||||
|
||||
|
||||
func show_credits() -> void:
|
||||
@@ -103,7 +120,8 @@ func show_credits() -> void:
|
||||
# Prevent concurrent scene changes
|
||||
if is_changing_scene:
|
||||
DebugManager.log_warn(
|
||||
"Scene change already in progress, ignoring show credits request", "GameManager"
|
||||
"Scene change already in progress, ignoring show credits request",
|
||||
"GameManager"
|
||||
)
|
||||
return
|
||||
|
||||
@@ -113,7 +131,8 @@ func show_credits() -> void:
|
||||
var packed_scene := load(CREDITS_SCENE_PATH)
|
||||
if not packed_scene or not packed_scene is PackedScene:
|
||||
DebugManager.log_error(
|
||||
"Failed to load Credits scene at: %s" % CREDITS_SCENE_PATH, "GameManager"
|
||||
"Failed to load Credits scene at: %s" % CREDITS_SCENE_PATH,
|
||||
"GameManager"
|
||||
)
|
||||
is_changing_scene = false
|
||||
return
|
||||
@@ -121,7 +140,8 @@ func show_credits() -> void:
|
||||
var result = get_tree().change_scene_to_packed(packed_scene)
|
||||
if result != OK:
|
||||
DebugManager.log_error(
|
||||
"Failed to change to credits scene (Error code: %d)" % result, "GameManager"
|
||||
"Failed to change to credits scene (Error code: %d)" % result,
|
||||
"GameManager"
|
||||
)
|
||||
is_changing_scene = false
|
||||
return
|
||||
@@ -137,8 +157,12 @@ func exit_to_main_menu() -> void:
|
||||
"""Exit to main menu with race condition protection"""
|
||||
# Prevent concurrent scene changes
|
||||
if is_changing_scene:
|
||||
DebugManager.log_warn(
|
||||
"Scene change already in progress, ignoring exit to main menu request", "GameManager"
|
||||
(
|
||||
DebugManager
|
||||
. log_warn(
|
||||
"Scene change already in progress, ignoring exit to main menu request",
|
||||
"GameManager"
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
@@ -147,14 +171,17 @@ func exit_to_main_menu() -> void:
|
||||
|
||||
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")
|
||||
DebugManager.log_error(
|
||||
"Failed to load Main scene at: %s" % MAIN_SCENE_PATH, "GameManager"
|
||||
)
|
||||
is_changing_scene = false
|
||||
return
|
||||
|
||||
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"
|
||||
"Failed to change to main scene (Error code: %d)" % result,
|
||||
"GameManager"
|
||||
)
|
||||
is_changing_scene = false
|
||||
return
|
||||
@@ -170,25 +197,33 @@ func _validate_game_mode_request(gameplay_mode: String) -> bool:
|
||||
"""Validate gameplay mode request with combined checks"""
|
||||
# Input validation
|
||||
if not gameplay_mode or gameplay_mode.is_empty():
|
||||
DebugManager.log_error("Empty or null gameplay mode provided", "GameManager")
|
||||
DebugManager.log_error(
|
||||
"Empty or null gameplay mode provided", "GameManager"
|
||||
)
|
||||
return false
|
||||
|
||||
if not gameplay_mode is String:
|
||||
DebugManager.log_error(
|
||||
"Invalid gameplay mode type: " + str(typeof(gameplay_mode)), "GameManager"
|
||||
"Invalid gameplay mode type: " + str(typeof(gameplay_mode)),
|
||||
"GameManager"
|
||||
)
|
||||
return false
|
||||
|
||||
# Prevent concurrent scene changes (race condition protection)
|
||||
if is_changing_scene:
|
||||
DebugManager.log_warn("Scene change already in progress, ignoring request", "GameManager")
|
||||
DebugManager.log_warn(
|
||||
"Scene change already in progress, ignoring request", "GameManager"
|
||||
)
|
||||
return false
|
||||
|
||||
# Validate gameplay mode
|
||||
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)],
|
||||
(
|
||||
"Invalid gameplay mode: '%s'. Valid modes: %s"
|
||||
% [gameplay_mode, str(valid_modes)]
|
||||
),
|
||||
"GameManager"
|
||||
)
|
||||
return false
|
||||
|
||||
@@ -42,7 +42,9 @@ func save_game() -> bool:
|
||||
"""Save current game data with race condition protection and error handling"""
|
||||
# Prevent concurrent saves
|
||||
if _save_in_progress:
|
||||
DebugManager.log_warn("Save already in progress, skipping", "SaveManager")
|
||||
DebugManager.log_warn(
|
||||
"Save already in progress, skipping", "SaveManager"
|
||||
)
|
||||
return false
|
||||
|
||||
_save_in_progress = true
|
||||
@@ -61,10 +63,13 @@ func _perform_save() -> bool:
|
||||
# Calculate checksum excluding _checksum field
|
||||
save_data["_checksum"] = _calculate_checksum(save_data)
|
||||
|
||||
var save_file: FileAccess = FileAccess.open(SAVE_FILE_PATH, FileAccess.WRITE)
|
||||
var save_file: FileAccess = FileAccess.open(
|
||||
SAVE_FILE_PATH, FileAccess.WRITE
|
||||
)
|
||||
if save_file == null:
|
||||
DebugManager.log_error(
|
||||
"Failed to open save file for writing: %s" % SAVE_FILE_PATH, "SaveManager"
|
||||
"Failed to open save file for writing: %s" % SAVE_FILE_PATH,
|
||||
"SaveManager"
|
||||
)
|
||||
return false
|
||||
|
||||
@@ -72,7 +77,9 @@ func _perform_save() -> bool:
|
||||
|
||||
# Validate JSON creation
|
||||
if json_string.is_empty():
|
||||
DebugManager.log_error("Failed to serialize save data to JSON", "SaveManager")
|
||||
DebugManager.log_error(
|
||||
"Failed to serialize save data to JSON", "SaveManager"
|
||||
)
|
||||
save_file.close()
|
||||
return false
|
||||
|
||||
@@ -92,7 +99,9 @@ func _perform_save() -> bool:
|
||||
func load_game() -> void:
|
||||
"""Load game data from disk with comprehensive validation and error recovery"""
|
||||
if not FileAccess.file_exists(SAVE_FILE_PATH):
|
||||
DebugManager.log_info("No save file found, using defaults", "SaveManager")
|
||||
DebugManager.log_info(
|
||||
"No save file found, using defaults", "SaveManager"
|
||||
)
|
||||
return
|
||||
|
||||
# Reset restore flag
|
||||
@@ -111,7 +120,8 @@ func _load_and_parse_save_file() -> Variant:
|
||||
var save_file: FileAccess = FileAccess.open(SAVE_FILE_PATH, FileAccess.READ)
|
||||
if save_file == null:
|
||||
DebugManager.log_error(
|
||||
"Failed to open save file for reading: %s" % SAVE_FILE_PATH, "SaveManager"
|
||||
"Failed to open save file for reading: %s" % SAVE_FILE_PATH,
|
||||
"SaveManager"
|
||||
)
|
||||
return null
|
||||
|
||||
@@ -119,7 +129,11 @@ func _load_and_parse_save_file() -> Variant:
|
||||
var file_size: int = save_file.get_length()
|
||||
if file_size > MAX_FILE_SIZE:
|
||||
DebugManager.log_error(
|
||||
"Save file too large: %d bytes (max %d)" % [file_size, MAX_FILE_SIZE], "SaveManager"
|
||||
(
|
||||
"Save file too large: %d bytes (max %d)"
|
||||
% [file_size, MAX_FILE_SIZE]
|
||||
),
|
||||
"SaveManager"
|
||||
)
|
||||
save_file.close()
|
||||
return null
|
||||
@@ -128,21 +142,26 @@ func _load_and_parse_save_file() -> Variant:
|
||||
save_file.close()
|
||||
|
||||
if not json_string is String:
|
||||
DebugManager.log_error("Save file contains invalid data type", "SaveManager")
|
||||
DebugManager.log_error(
|
||||
"Save file contains invalid data type", "SaveManager"
|
||||
)
|
||||
return null
|
||||
|
||||
var json: JSON = JSON.new()
|
||||
var parse_result: Error = json.parse(json_string)
|
||||
if parse_result != OK:
|
||||
DebugManager.log_error(
|
||||
"Failed to parse save file JSON: %s" % json.error_string, "SaveManager"
|
||||
"Failed to parse save file JSON: %s" % json.error_string,
|
||||
"SaveManager"
|
||||
)
|
||||
_handle_load_failure("JSON parse failed")
|
||||
return null
|
||||
|
||||
var loaded_data: Variant = json.data
|
||||
if not loaded_data is Dictionary:
|
||||
DebugManager.log_error("Save file root is not a dictionary", "SaveManager")
|
||||
DebugManager.log_error(
|
||||
"Save file root is not a dictionary", "SaveManager"
|
||||
)
|
||||
_handle_load_failure("Invalid data format")
|
||||
return null
|
||||
|
||||
@@ -154,7 +173,8 @@ func _process_loaded_data(loaded_data: Variant) -> void:
|
||||
# Validate checksum first
|
||||
if not _validate_checksum(loaded_data):
|
||||
DebugManager.log_error(
|
||||
"Save file checksum validation failed - possible tampering", "SaveManager"
|
||||
"Save file checksum validation failed - possible tampering",
|
||||
"SaveManager"
|
||||
)
|
||||
_handle_load_failure("Checksum validation failed")
|
||||
return
|
||||
@@ -162,14 +182,17 @@ func _process_loaded_data(loaded_data: Variant) -> void:
|
||||
# Handle version migration
|
||||
var migrated_data: Variant = _handle_version_migration(loaded_data)
|
||||
if migrated_data == null:
|
||||
DebugManager.log_error("Save file version migration failed", "SaveManager")
|
||||
DebugManager.log_error(
|
||||
"Save file version migration failed", "SaveManager"
|
||||
)
|
||||
_handle_load_failure("Migration failed")
|
||||
return
|
||||
|
||||
# Validate and fix loaded data
|
||||
if not _validate_and_fix_save_data(migrated_data):
|
||||
DebugManager.log_error(
|
||||
"Save file failed validation after migration, using defaults", "SaveManager"
|
||||
"Save file failed validation after migration, using defaults",
|
||||
"SaveManager"
|
||||
)
|
||||
_handle_load_failure("Validation failed")
|
||||
return
|
||||
@@ -184,7 +207,8 @@ func _handle_load_failure(reason: String) -> void:
|
||||
var backup_restored = _restore_backup_if_exists()
|
||||
if not backup_restored:
|
||||
DebugManager.log_warn(
|
||||
"%s and backup restore failed, using defaults" % reason, "SaveManager"
|
||||
"%s and backup restore failed, using defaults" % reason,
|
||||
"SaveManager"
|
||||
)
|
||||
|
||||
DebugManager.log_info(
|
||||
@@ -199,11 +223,14 @@ func _handle_load_failure(reason: String) -> void:
|
||||
func update_current_score(score: int) -> void:
|
||||
# Input validation
|
||||
if score < 0:
|
||||
DebugManager.log_warn("Negative score rejected: %d" % score, "SaveManager")
|
||||
DebugManager.log_warn(
|
||||
"Negative score rejected: %d" % score, "SaveManager"
|
||||
)
|
||||
return
|
||||
if score > MAX_SCORE:
|
||||
DebugManager.log_warn(
|
||||
"Score too high, capping at maximum: %d -> %d" % [score, MAX_SCORE], "SaveManager"
|
||||
"Score too high, capping at maximum: %d -> %d" % [score, MAX_SCORE],
|
||||
"SaveManager"
|
||||
)
|
||||
score = MAX_SCORE
|
||||
|
||||
@@ -219,23 +246,33 @@ func start_new_game() -> void:
|
||||
# Clear saved grid state
|
||||
game_data.grid_state.grid_layout = []
|
||||
DebugManager.log_info(
|
||||
"Started new game #%d (cleared grid state)" % game_data.games_played, "SaveManager"
|
||||
"Started new game #%d (cleared grid state)" % game_data.games_played,
|
||||
"SaveManager"
|
||||
)
|
||||
|
||||
|
||||
func finish_game(final_score: int) -> void:
|
||||
# Input validation
|
||||
if final_score < 0:
|
||||
DebugManager.log_warn("Negative final score rejected: %d" % final_score, "SaveManager")
|
||||
DebugManager.log_warn(
|
||||
"Negative final score rejected: %d" % final_score, "SaveManager"
|
||||
)
|
||||
return
|
||||
if final_score > MAX_SCORE:
|
||||
DebugManager.log_warn(
|
||||
"Final score too high, capping: %d -> %d" % [final_score, MAX_SCORE], "SaveManager"
|
||||
(
|
||||
"Final score too high, capping: %d -> %d"
|
||||
% [final_score, MAX_SCORE]
|
||||
),
|
||||
"SaveManager"
|
||||
)
|
||||
final_score = MAX_SCORE
|
||||
|
||||
DebugManager.log_info(
|
||||
"Finishing game with score: %d (previous: %d)" % [final_score, game_data.current_score],
|
||||
(
|
||||
"Finishing game with score: %d (previous: %d)"
|
||||
% [final_score, game_data.current_score]
|
||||
),
|
||||
"SaveManager"
|
||||
)
|
||||
game_data.current_score = final_score
|
||||
@@ -250,7 +287,9 @@ func finish_game(final_score: int) -> void:
|
||||
|
||||
if final_score > game_data.high_score:
|
||||
game_data.high_score = final_score
|
||||
DebugManager.log_info("New high score achieved: %d" % final_score, "SaveManager")
|
||||
DebugManager.log_info(
|
||||
"New high score achieved: %d" % final_score, "SaveManager"
|
||||
)
|
||||
save_game()
|
||||
|
||||
|
||||
@@ -271,11 +310,18 @@ func get_total_score() -> int:
|
||||
|
||||
|
||||
func save_grid_state(
|
||||
grid_size: Vector2i, tile_types_count: int, active_gem_types: Array, grid_layout: Array
|
||||
grid_size: Vector2i,
|
||||
tile_types_count: int,
|
||||
active_gem_types: Array,
|
||||
grid_layout: Array
|
||||
) -> void:
|
||||
# Input validation
|
||||
if not _validate_grid_parameters(grid_size, tile_types_count, active_gem_types, grid_layout):
|
||||
DebugManager.log_error("Grid state validation failed, not saving", "SaveManager")
|
||||
if not _validate_grid_parameters(
|
||||
grid_size, tile_types_count, active_gem_types, grid_layout
|
||||
):
|
||||
DebugManager.log_error(
|
||||
"Grid state validation failed, not saving", "SaveManager"
|
||||
)
|
||||
return
|
||||
|
||||
DebugManager.log_info(
|
||||
@@ -338,10 +384,13 @@ func reset_all_progress() -> bool:
|
||||
if FileAccess.file_exists(SAVE_FILE_PATH):
|
||||
var error: Error = DirAccess.remove_absolute(SAVE_FILE_PATH)
|
||||
if error == OK:
|
||||
DebugManager.log_info("Main save file deleted successfully", "SaveManager")
|
||||
DebugManager.log_info(
|
||||
"Main save file deleted successfully", "SaveManager"
|
||||
)
|
||||
else:
|
||||
DebugManager.log_error(
|
||||
"Failed to delete main save file: error %d" % error, "SaveManager"
|
||||
"Failed to delete main save file: error %d" % error,
|
||||
"SaveManager"
|
||||
)
|
||||
|
||||
# Delete backup file
|
||||
@@ -349,21 +398,27 @@ func reset_all_progress() -> bool:
|
||||
if FileAccess.file_exists(backup_path):
|
||||
var error: Error = DirAccess.remove_absolute(backup_path)
|
||||
if error == OK:
|
||||
DebugManager.log_info("Backup save file deleted successfully", "SaveManager")
|
||||
DebugManager.log_info(
|
||||
"Backup save file deleted successfully", "SaveManager"
|
||||
)
|
||||
else:
|
||||
DebugManager.log_error(
|
||||
"Failed to delete backup save file: error %d" % error, "SaveManager"
|
||||
"Failed to delete backup save file: error %d" % error,
|
||||
"SaveManager"
|
||||
)
|
||||
|
||||
DebugManager.log_info(
|
||||
"Progress reset completed - all scores and save data cleared", "SaveManager"
|
||||
"Progress reset completed - all scores and save data cleared",
|
||||
"SaveManager"
|
||||
)
|
||||
|
||||
# Clear restore flag
|
||||
_restore_in_progress = false
|
||||
|
||||
# Create fresh save file with default data
|
||||
DebugManager.log_info("Creating fresh save file with default data", "SaveManager")
|
||||
DebugManager.log_info(
|
||||
"Creating fresh save file with default data", "SaveManager"
|
||||
)
|
||||
save_game()
|
||||
|
||||
return true
|
||||
@@ -395,11 +450,17 @@ func _validate_save_data(data: Dictionary) -> bool:
|
||||
func _validate_required_fields(data: Dictionary) -> bool:
|
||||
"""Validate that all required fields exist"""
|
||||
var required_fields: Array[String] = [
|
||||
"high_score", "current_score", "games_played", "total_score", "grid_state"
|
||||
"high_score",
|
||||
"current_score",
|
||||
"games_played",
|
||||
"total_score",
|
||||
"grid_state"
|
||||
]
|
||||
for field in required_fields:
|
||||
if not data.has(field):
|
||||
DebugManager.log_error("Missing required field: %s" % field, "SaveManager")
|
||||
DebugManager.log_error(
|
||||
"Missing required field: %s" % field, "SaveManager"
|
||||
)
|
||||
return false
|
||||
return true
|
||||
|
||||
@@ -409,7 +470,9 @@ func _validate_score_fields(data: Dictionary) -> bool:
|
||||
var score_fields = ["high_score", "current_score", "total_score"]
|
||||
for field in score_fields:
|
||||
if not _is_valid_score(data.get(field, 0)):
|
||||
DebugManager.log_error("Invalid %s validation failed" % field, "SaveManager")
|
||||
DebugManager.log_error(
|
||||
"Invalid %s validation failed" % field, "SaveManager"
|
||||
)
|
||||
return false
|
||||
return true
|
||||
|
||||
@@ -419,7 +482,10 @@ func _validate_games_played_field(data: Dictionary) -> bool:
|
||||
var games_played: Variant = data.get("games_played", 0)
|
||||
if not (games_played is int or games_played is float):
|
||||
DebugManager.log_error(
|
||||
"Invalid games_played type: %s (type: %s)" % [str(games_played), typeof(games_played)],
|
||||
(
|
||||
"Invalid games_played type: %s (type: %s)"
|
||||
% [str(games_played), typeof(games_played)]
|
||||
),
|
||||
"SaveManager"
|
||||
)
|
||||
return false
|
||||
@@ -427,14 +493,18 @@ func _validate_games_played_field(data: Dictionary) -> bool:
|
||||
# Check for NaN/Infinity in games_played if it's a float
|
||||
if games_played is float and (is_nan(games_played) or is_inf(games_played)):
|
||||
DebugManager.log_error(
|
||||
"Invalid games_played float value: %s" % str(games_played), "SaveManager"
|
||||
"Invalid games_played float value: %s" % str(games_played),
|
||||
"SaveManager"
|
||||
)
|
||||
return false
|
||||
|
||||
var games_played_int: int = int(games_played)
|
||||
if games_played_int < 0 or games_played_int > MAX_GAMES_PLAYED:
|
||||
DebugManager.log_error(
|
||||
"Invalid games_played value: %d (range: 0-%d)" % [games_played_int, MAX_GAMES_PLAYED],
|
||||
(
|
||||
"Invalid games_played value: %d (range: 0-%d)"
|
||||
% [games_played_int, MAX_GAMES_PLAYED]
|
||||
),
|
||||
"SaveManager"
|
||||
)
|
||||
return false
|
||||
@@ -447,16 +517,23 @@ func _validate_and_fix_save_data(data: Dictionary) -> bool:
|
||||
Permissive validation that fixes issues instead of rejecting data entirely.
|
||||
Used during migration to preserve as much user data as possible.
|
||||
"""
|
||||
DebugManager.log_info("Running permissive validation with auto-fix", "SaveManager")
|
||||
DebugManager.log_info(
|
||||
"Running permissive validation with auto-fix", "SaveManager"
|
||||
)
|
||||
|
||||
# Ensure all required fields exist, create defaults if missing
|
||||
var required_fields: Array[String] = [
|
||||
"high_score", "current_score", "games_played", "total_score", "grid_state"
|
||||
"high_score",
|
||||
"current_score",
|
||||
"games_played",
|
||||
"total_score",
|
||||
"grid_state"
|
||||
]
|
||||
for field in required_fields:
|
||||
if not data.has(field):
|
||||
DebugManager.log_warn(
|
||||
"Missing required field '%s', adding default value" % field, "SaveManager"
|
||||
"Missing required field '%s', adding default value" % field,
|
||||
"SaveManager"
|
||||
)
|
||||
match field:
|
||||
"high_score", "current_score", "total_score":
|
||||
@@ -475,15 +552,21 @@ func _validate_and_fix_save_data(data: Dictionary) -> bool:
|
||||
for field in ["high_score", "current_score", "total_score"]:
|
||||
var value: Variant = data.get(field, 0)
|
||||
if not (value is int or value is float):
|
||||
DebugManager.log_warn("Invalid type for %s, converting to 0" % field, "SaveManager")
|
||||
DebugManager.log_warn(
|
||||
"Invalid type for %s, converting to 0" % field, "SaveManager"
|
||||
)
|
||||
data[field] = 0
|
||||
else:
|
||||
var numeric_value: int = int(value)
|
||||
if numeric_value < 0:
|
||||
DebugManager.log_warn("Negative %s fixed to 0" % field, "SaveManager")
|
||||
DebugManager.log_warn(
|
||||
"Negative %s fixed to 0" % field, "SaveManager"
|
||||
)
|
||||
data[field] = 0
|
||||
elif numeric_value > MAX_SCORE:
|
||||
DebugManager.log_warn("%s too high, clamped to maximum" % field, "SaveManager")
|
||||
DebugManager.log_warn(
|
||||
"%s too high, clamped to maximum" % field, "SaveManager"
|
||||
)
|
||||
data[field] = MAX_SCORE
|
||||
else:
|
||||
data[field] = numeric_value
|
||||
@@ -491,7 +574,9 @@ func _validate_and_fix_save_data(data: Dictionary) -> bool:
|
||||
# Fix games_played
|
||||
var games_played: Variant = data.get("games_played", 0)
|
||||
if not (games_played is int or games_played is float):
|
||||
DebugManager.log_warn("Invalid games_played type, converting to 0", "SaveManager")
|
||||
DebugManager.log_warn(
|
||||
"Invalid games_played type, converting to 0", "SaveManager"
|
||||
)
|
||||
data["games_played"] = 0
|
||||
else:
|
||||
var games_played_int: int = int(games_played)
|
||||
@@ -505,7 +590,9 @@ func _validate_and_fix_save_data(data: Dictionary) -> bool:
|
||||
# Fix grid_state - ensure it exists and has basic structure
|
||||
var grid_state: Variant = data.get("grid_state", {})
|
||||
if not grid_state is Dictionary:
|
||||
DebugManager.log_warn("Invalid grid_state, creating default", "SaveManager")
|
||||
DebugManager.log_warn(
|
||||
"Invalid grid_state, creating default", "SaveManager"
|
||||
)
|
||||
data["grid_state"] = {
|
||||
"grid_size": {"x": 8, "y": 8},
|
||||
"tile_types_count": 5,
|
||||
@@ -514,24 +601,48 @@ func _validate_and_fix_save_data(data: Dictionary) -> bool:
|
||||
}
|
||||
else:
|
||||
# Fix grid_state fields if they're missing or invalid
|
||||
if not grid_state.has("grid_size") or not grid_state.grid_size is Dictionary:
|
||||
DebugManager.log_warn("Invalid grid_size, using default", "SaveManager")
|
||||
if (
|
||||
not grid_state.has("grid_size")
|
||||
or not grid_state.grid_size is Dictionary
|
||||
):
|
||||
DebugManager.log_warn(
|
||||
"Invalid grid_size, using default", "SaveManager"
|
||||
)
|
||||
grid_state["grid_size"] = {"x": 8, "y": 8}
|
||||
|
||||
if not grid_state.has("tile_types_count") or not grid_state.tile_types_count is int:
|
||||
DebugManager.log_warn("Invalid tile_types_count, using default", "SaveManager")
|
||||
if (
|
||||
not grid_state.has("tile_types_count")
|
||||
or not grid_state.tile_types_count is int
|
||||
):
|
||||
DebugManager.log_warn(
|
||||
"Invalid tile_types_count, using default", "SaveManager"
|
||||
)
|
||||
grid_state["tile_types_count"] = 5
|
||||
|
||||
if not grid_state.has("active_gem_types") or not grid_state.active_gem_types is Array:
|
||||
DebugManager.log_warn("Invalid active_gem_types, using default", "SaveManager")
|
||||
if (
|
||||
not grid_state.has("active_gem_types")
|
||||
or not grid_state.active_gem_types is Array
|
||||
):
|
||||
DebugManager.log_warn(
|
||||
"Invalid active_gem_types, using default", "SaveManager"
|
||||
)
|
||||
grid_state["active_gem_types"] = [0, 1, 2, 3, 4]
|
||||
|
||||
if not grid_state.has("grid_layout") or not grid_state.grid_layout is Array:
|
||||
DebugManager.log_warn("Invalid grid_layout, clearing saved grid", "SaveManager")
|
||||
if (
|
||||
not grid_state.has("grid_layout")
|
||||
or not grid_state.grid_layout is Array
|
||||
):
|
||||
DebugManager.log_warn(
|
||||
"Invalid grid_layout, clearing saved grid", "SaveManager"
|
||||
)
|
||||
grid_state["grid_layout"] = []
|
||||
|
||||
DebugManager.log_info(
|
||||
"Permissive validation completed - data has been fixed and will be loaded", "SaveManager"
|
||||
(
|
||||
DebugManager
|
||||
. log_info(
|
||||
"Permissive validation completed - data has been fixed and will be loaded",
|
||||
"SaveManager"
|
||||
)
|
||||
)
|
||||
return true
|
||||
|
||||
@@ -569,7 +680,10 @@ func _validate_grid_size(grid_state: Dictionary) -> Dictionary:
|
||||
"""Validate grid size and return validation result with dimensions"""
|
||||
var result = {"valid": false, "width": 0, "height": 0}
|
||||
|
||||
if not grid_state.has("grid_size") or not grid_state.grid_size is Dictionary:
|
||||
if (
|
||||
not grid_state.has("grid_size")
|
||||
or not grid_state.grid_size is Dictionary
|
||||
):
|
||||
DebugManager.log_error("Invalid grid_size in save data", "SaveManager")
|
||||
return result
|
||||
|
||||
@@ -581,8 +695,15 @@ func _validate_grid_size(grid_state: Dictionary) -> Dictionary:
|
||||
var height: Variant = size.y
|
||||
if not width is int or not height is int:
|
||||
return result
|
||||
if width < 3 or height < 3 or width > MAX_GRID_SIZE or height > MAX_GRID_SIZE:
|
||||
DebugManager.log_error("Grid size out of bounds: %dx%d" % [width, height], "SaveManager")
|
||||
if (
|
||||
width < 3
|
||||
or height < 3
|
||||
or width > MAX_GRID_SIZE
|
||||
or height > MAX_GRID_SIZE
|
||||
):
|
||||
DebugManager.log_error(
|
||||
"Grid size out of bounds: %dx%d" % [width, height], "SaveManager"
|
||||
)
|
||||
return result
|
||||
|
||||
result.valid = true
|
||||
@@ -595,16 +716,22 @@ func _validate_tile_types(grid_state: Dictionary) -> int:
|
||||
"""Validate tile types count and return it, or -1 if invalid"""
|
||||
var tile_types: Variant = grid_state.get("tile_types_count", 0)
|
||||
if not tile_types is int or tile_types < 3 or tile_types > MAX_TILE_TYPES:
|
||||
DebugManager.log_error("Invalid tile_types_count: %s" % str(tile_types), "SaveManager")
|
||||
DebugManager.log_error(
|
||||
"Invalid tile_types_count: %s" % str(tile_types), "SaveManager"
|
||||
)
|
||||
return -1
|
||||
return tile_types
|
||||
|
||||
|
||||
func _validate_active_gem_types(grid_state: Dictionary, tile_types: int) -> bool:
|
||||
func _validate_active_gem_types(
|
||||
grid_state: Dictionary, tile_types: int
|
||||
) -> bool:
|
||||
"""Validate active gem types array"""
|
||||
var active_gems: Variant = grid_state.get("active_gem_types", [])
|
||||
if not active_gems is Array:
|
||||
DebugManager.log_error("active_gem_types is not an array", "SaveManager")
|
||||
DebugManager.log_error(
|
||||
"active_gem_types is not an array", "SaveManager"
|
||||
)
|
||||
return false
|
||||
|
||||
# If active_gem_types exists, validate its contents
|
||||
@@ -613,12 +740,17 @@ func _validate_active_gem_types(grid_state: Dictionary, tile_types: int) -> bool
|
||||
var gem_type: Variant = active_gems[i]
|
||||
if not gem_type is int:
|
||||
DebugManager.log_error(
|
||||
"active_gem_types[%d] is not an integer: %s" % [i, str(gem_type)], "SaveManager"
|
||||
(
|
||||
"active_gem_types[%d] is not an integer: %s"
|
||||
% [i, str(gem_type)]
|
||||
),
|
||||
"SaveManager"
|
||||
)
|
||||
return false
|
||||
if gem_type < 0 or gem_type >= tile_types:
|
||||
DebugManager.log_error(
|
||||
"active_gem_types[%d] out of range: %d" % [i, gem_type], "SaveManager"
|
||||
"active_gem_types[%d] out of range: %d" % [i, gem_type],
|
||||
"SaveManager"
|
||||
)
|
||||
return false
|
||||
return true
|
||||
@@ -629,7 +761,10 @@ func _validate_grid_layout(
|
||||
) -> bool:
|
||||
if layout.size() != expected_height:
|
||||
DebugManager.log_error(
|
||||
"Grid layout height mismatch: %d vs %d" % [layout.size(), expected_height],
|
||||
(
|
||||
"Grid layout height mismatch: %d vs %d"
|
||||
% [layout.size(), expected_height]
|
||||
),
|
||||
"SaveManager"
|
||||
)
|
||||
return false
|
||||
@@ -637,11 +772,16 @@ func _validate_grid_layout(
|
||||
for y in range(layout.size()):
|
||||
var row: Variant = layout[y]
|
||||
if not row is Array:
|
||||
DebugManager.log_error("Grid layout row %d is not an array" % y, "SaveManager")
|
||||
DebugManager.log_error(
|
||||
"Grid layout row %d is not an array" % y, "SaveManager"
|
||||
)
|
||||
return false
|
||||
if row.size() != expected_width:
|
||||
DebugManager.log_error(
|
||||
"Grid layout row %d width mismatch: %d vs %d" % [y, row.size(), expected_width],
|
||||
(
|
||||
"Grid layout row %d width mismatch: %d vs %d"
|
||||
% [y, row.size(), expected_width]
|
||||
),
|
||||
"SaveManager"
|
||||
)
|
||||
return false
|
||||
@@ -650,13 +790,20 @@ func _validate_grid_layout(
|
||||
var tile_type: Variant = row[x]
|
||||
if not tile_type is int:
|
||||
DebugManager.log_error(
|
||||
"Grid tile [%d][%d] is not an integer: %s" % [y, x, str(tile_type)],
|
||||
(
|
||||
"Grid tile [%d][%d] is not an integer: %s"
|
||||
% [y, x, str(tile_type)]
|
||||
),
|
||||
"SaveManager"
|
||||
)
|
||||
return false
|
||||
if tile_type < -1 or tile_type >= max_tile_type:
|
||||
DebugManager.log_error(
|
||||
"Grid tile [%d][%d] type out of range: %d" % [y, x, tile_type], "SaveManager"
|
||||
(
|
||||
"Grid tile [%d][%d] type out of range: %d"
|
||||
% [y, x, tile_type]
|
||||
),
|
||||
"SaveManager"
|
||||
)
|
||||
return false
|
||||
|
||||
@@ -664,7 +811,10 @@ func _validate_grid_layout(
|
||||
|
||||
|
||||
func _validate_grid_parameters(
|
||||
grid_size: Vector2i, tile_types_count: int, active_gem_types: Array, grid_layout: Array
|
||||
grid_size: Vector2i,
|
||||
tile_types_count: int,
|
||||
active_gem_types: Array,
|
||||
grid_layout: Array
|
||||
) -> bool:
|
||||
# Validate grid size
|
||||
if (
|
||||
@@ -685,7 +835,10 @@ func _validate_grid_parameters(
|
||||
# Validate tile types count
|
||||
if tile_types_count < 3 or tile_types_count > MAX_TILE_TYPES:
|
||||
DebugManager.log_error(
|
||||
"Invalid tile types count: %d (min 3, max %d)" % [tile_types_count, MAX_TILE_TYPES],
|
||||
(
|
||||
"Invalid tile types count: %d (min 3, max %d)"
|
||||
% [tile_types_count, MAX_TILE_TYPES]
|
||||
),
|
||||
"SaveManager"
|
||||
)
|
||||
return false
|
||||
@@ -702,14 +855,20 @@ func _validate_grid_parameters(
|
||||
return false
|
||||
|
||||
# Validate grid layout
|
||||
return _validate_grid_layout(grid_layout, grid_size.x, grid_size.y, tile_types_count)
|
||||
return _validate_grid_layout(
|
||||
grid_layout, grid_size.x, grid_size.y, tile_types_count
|
||||
)
|
||||
|
||||
|
||||
func _is_valid_score(score: Variant) -> bool:
|
||||
# Accept both int and float, but convert to int for validation
|
||||
if not (score is int or score is float):
|
||||
DebugManager.log_error(
|
||||
"Score is not a number: %s (type: %s)" % [str(score), typeof(score)], "SaveManager"
|
||||
(
|
||||
"Score is not a number: %s (type: %s)"
|
||||
% [str(score), typeof(score)]
|
||||
),
|
||||
"SaveManager"
|
||||
)
|
||||
return false
|
||||
|
||||
@@ -717,13 +876,16 @@ func _is_valid_score(score: Variant) -> bool:
|
||||
if score is float:
|
||||
if is_nan(score) or is_inf(score):
|
||||
DebugManager.log_error(
|
||||
"Score contains invalid float value (NaN/Inf): %s" % str(score), "SaveManager"
|
||||
"Score contains invalid float value (NaN/Inf): %s" % str(score),
|
||||
"SaveManager"
|
||||
)
|
||||
return false
|
||||
|
||||
var score_int = int(score)
|
||||
if score_int < 0 or score_int > MAX_SCORE:
|
||||
DebugManager.log_error("Score out of bounds: %d" % score_int, "SaveManager")
|
||||
DebugManager.log_error(
|
||||
"Score out of bounds: %d" % score_int, "SaveManager"
|
||||
)
|
||||
return false
|
||||
return true
|
||||
|
||||
@@ -737,12 +899,16 @@ func _merge_validated_data(loaded_data: Dictionary) -> void:
|
||||
|
||||
# Games played should always be an integer
|
||||
if loaded_data.has("games_played"):
|
||||
game_data["games_played"] = _safe_get_numeric_value(loaded_data, "games_played", 0)
|
||||
game_data["games_played"] = _safe_get_numeric_value(
|
||||
loaded_data, "games_played", 0
|
||||
)
|
||||
|
||||
# Merge grid state carefully
|
||||
var loaded_grid: Variant = loaded_data.get("grid_state", {})
|
||||
if loaded_grid is Dictionary:
|
||||
for grid_key in ["grid_size", "tile_types_count", "active_gem_types", "grid_layout"]:
|
||||
for grid_key in [
|
||||
"grid_size", "tile_types_count", "active_gem_types", "grid_layout"
|
||||
]:
|
||||
if loaded_grid.has(grid_key):
|
||||
game_data.grid_state[grid_key] = loaded_grid[grid_key]
|
||||
|
||||
@@ -844,15 +1010,22 @@ func _validate_checksum(data: Dictionary) -> bool:
|
||||
# Try to be more lenient with existing saves to prevent data loss
|
||||
var data_version: Variant = data.get("_version", 0)
|
||||
if data_version <= 1:
|
||||
DebugManager.log_warn(
|
||||
(
|
||||
"Checksum mismatch in v%d save file - may be due to JSON serialization issue "
|
||||
+ (
|
||||
"(stored: %s, calculated: %s)"
|
||||
% [data_version, stored_checksum, calculated_checksum]
|
||||
)
|
||||
),
|
||||
"SaveManager"
|
||||
(
|
||||
DebugManager
|
||||
. log_warn(
|
||||
(
|
||||
"Checksum mismatch in v%d save file - may be due to JSON serialization issue "
|
||||
+ (
|
||||
"(stored: %s, calculated: %s)"
|
||||
% [
|
||||
data_version,
|
||||
stored_checksum,
|
||||
calculated_checksum
|
||||
]
|
||||
)
|
||||
),
|
||||
"SaveManager"
|
||||
)
|
||||
)
|
||||
(
|
||||
DebugManager
|
||||
@@ -876,7 +1049,9 @@ func _validate_checksum(data: Dictionary) -> bool:
|
||||
return is_valid
|
||||
|
||||
|
||||
func _safe_get_numeric_value(data: Dictionary, key: String, default_value: float) -> int:
|
||||
func _safe_get_numeric_value(
|
||||
data: Dictionary, key: String, default_value: float
|
||||
) -> int:
|
||||
"""Safely extract and convert numeric values with comprehensive validation"""
|
||||
var value: Variant = data.get(key, default_value)
|
||||
|
||||
@@ -910,13 +1085,15 @@ func _safe_get_numeric_value(data: Dictionary, key: String, default_value: float
|
||||
if key in ["high_score", "current_score", "total_score"]:
|
||||
if int_value < 0 or int_value > MAX_SCORE:
|
||||
DebugManager.log_warn(
|
||||
"Score %s out of bounds: %d, using default" % [key, int_value], "SaveManager"
|
||||
"Score %s out of bounds: %d, using default" % [key, int_value],
|
||||
"SaveManager"
|
||||
)
|
||||
return int(default_value)
|
||||
elif key == "games_played":
|
||||
if int_value < 0 or int_value > MAX_GAMES_PLAYED:
|
||||
DebugManager.log_warn(
|
||||
"Games played out of bounds: %d, using default" % int_value, "SaveManager"
|
||||
"Games played out of bounds: %d, using default" % int_value,
|
||||
"SaveManager"
|
||||
)
|
||||
return int(default_value)
|
||||
|
||||
@@ -930,7 +1107,8 @@ func _handle_version_migration(data: Dictionary) -> Variant:
|
||||
if data_version == SAVE_FORMAT_VERSION:
|
||||
# Current version, no migration needed
|
||||
DebugManager.log_info(
|
||||
"Save file is current version (%d)" % SAVE_FORMAT_VERSION, "SaveManager"
|
||||
"Save file is current version (%d)" % SAVE_FORMAT_VERSION,
|
||||
"SaveManager"
|
||||
)
|
||||
return data
|
||||
if data_version > SAVE_FORMAT_VERSION:
|
||||
@@ -945,7 +1123,10 @@ func _handle_version_migration(data: Dictionary) -> Variant:
|
||||
return null
|
||||
# Older version - migrate
|
||||
DebugManager.log_info(
|
||||
"Migrating save data from version %d to %d" % [data_version, SAVE_FORMAT_VERSION],
|
||||
(
|
||||
"Migrating save data from version %d to %d"
|
||||
% [data_version, SAVE_FORMAT_VERSION]
|
||||
),
|
||||
"SaveManager"
|
||||
)
|
||||
return _migrate_save_data(data, data_version)
|
||||
@@ -960,7 +1141,9 @@ func _migrate_save_data(data: Dictionary, from_version: int) -> Dictionary:
|
||||
# Add new fields that didn't exist in version 0
|
||||
if not migrated_data.has("total_score"):
|
||||
migrated_data["total_score"] = 0
|
||||
DebugManager.log_info("Added total_score field during migration", "SaveManager")
|
||||
DebugManager.log_info(
|
||||
"Added total_score field during migration", "SaveManager"
|
||||
)
|
||||
|
||||
if not migrated_data.has("grid_state"):
|
||||
migrated_data["grid_state"] = {
|
||||
@@ -969,7 +1152,9 @@ func _migrate_save_data(data: Dictionary, from_version: int) -> Dictionary:
|
||||
"active_gem_types": [0, 1, 2, 3, 4],
|
||||
"grid_layout": []
|
||||
}
|
||||
DebugManager.log_info("Added grid_state structure during migration", "SaveManager")
|
||||
DebugManager.log_info(
|
||||
"Added grid_state structure during migration", "SaveManager"
|
||||
)
|
||||
|
||||
# Ensure all numeric values are within bounds after migration
|
||||
for score_key in ["high_score", "current_score", "total_score"]:
|
||||
@@ -981,11 +1166,17 @@ func _migrate_save_data(data: Dictionary, from_version: int) -> Dictionary:
|
||||
DebugManager.log_warn(
|
||||
(
|
||||
"Clamping %s during migration: %d -> %d"
|
||||
% [score_key, int_score, clamp(int_score, 0, MAX_SCORE)]
|
||||
% [
|
||||
score_key,
|
||||
int_score,
|
||||
clamp(int_score, 0, MAX_SCORE)
|
||||
]
|
||||
),
|
||||
"SaveManager"
|
||||
)
|
||||
migrated_data[score_key] = clamp(int_score, 0, MAX_SCORE)
|
||||
migrated_data[score_key] = clamp(
|
||||
int_score, 0, MAX_SCORE
|
||||
)
|
||||
|
||||
# Future migrations would go here
|
||||
# if from_version < 2:
|
||||
@@ -997,7 +1188,9 @@ func _migrate_save_data(data: Dictionary, from_version: int) -> Dictionary:
|
||||
# Recalculate checksum after migration
|
||||
migrated_data["_checksum"] = _calculate_checksum(migrated_data)
|
||||
|
||||
DebugManager.log_info("Save data migration completed successfully", "SaveManager")
|
||||
DebugManager.log_info(
|
||||
"Save data migration completed successfully", "SaveManager"
|
||||
)
|
||||
return migrated_data
|
||||
|
||||
|
||||
@@ -1005,7 +1198,9 @@ func _create_backup() -> void:
|
||||
# Create backup of current save file
|
||||
if FileAccess.file_exists(SAVE_FILE_PATH):
|
||||
var backup_path: String = SAVE_FILE_PATH + ".backup"
|
||||
var original: FileAccess = FileAccess.open(SAVE_FILE_PATH, FileAccess.READ)
|
||||
var original: FileAccess = FileAccess.open(
|
||||
SAVE_FILE_PATH, FileAccess.READ
|
||||
)
|
||||
var backup: FileAccess = FileAccess.open(backup_path, FileAccess.WRITE)
|
||||
if original and backup:
|
||||
backup.store_var(original.get_var())
|
||||
@@ -1017,7 +1212,9 @@ func _create_backup() -> void:
|
||||
func _restore_backup_if_exists() -> bool:
|
||||
var backup_path: String = SAVE_FILE_PATH + ".backup"
|
||||
if not FileAccess.file_exists(backup_path):
|
||||
DebugManager.log_warn("No backup file found for recovery", "SaveManager")
|
||||
DebugManager.log_warn(
|
||||
"No backup file found for recovery", "SaveManager"
|
||||
)
|
||||
return false
|
||||
|
||||
DebugManager.log_info("Attempting to restore from backup", "SaveManager")
|
||||
@@ -1025,12 +1222,16 @@ func _restore_backup_if_exists() -> bool:
|
||||
# Validate backup file size before attempting restore
|
||||
var backup_file: FileAccess = FileAccess.open(backup_path, FileAccess.READ)
|
||||
if backup_file == null:
|
||||
DebugManager.log_error("Failed to open backup file for reading", "SaveManager")
|
||||
DebugManager.log_error(
|
||||
"Failed to open backup file for reading", "SaveManager"
|
||||
)
|
||||
return false
|
||||
|
||||
var backup_size: int = backup_file.get_length()
|
||||
if backup_size > MAX_FILE_SIZE:
|
||||
DebugManager.log_error("Backup file too large: %d bytes" % backup_size, "SaveManager")
|
||||
DebugManager.log_error(
|
||||
"Backup file too large: %d bytes" % backup_size, "SaveManager"
|
||||
)
|
||||
backup_file.close()
|
||||
return false
|
||||
|
||||
@@ -1045,13 +1246,17 @@ func _restore_backup_if_exists() -> bool:
|
||||
# Create new save file from backup
|
||||
var original: FileAccess = FileAccess.open(SAVE_FILE_PATH, FileAccess.WRITE)
|
||||
if original == null:
|
||||
DebugManager.log_error("Failed to create new save file from backup", "SaveManager")
|
||||
DebugManager.log_error(
|
||||
"Failed to create new save file from backup", "SaveManager"
|
||||
)
|
||||
return false
|
||||
|
||||
original.store_var(backup_data)
|
||||
original.close()
|
||||
|
||||
DebugManager.log_info("Backup restored successfully to main save file", "SaveManager")
|
||||
DebugManager.log_info(
|
||||
"Backup restored successfully to main save file", "SaveManager"
|
||||
)
|
||||
# Note: The restored file will be loaded on the next game restart
|
||||
# We don't recursively load here to prevent infinite loops
|
||||
return true
|
||||
|
||||
@@ -10,7 +10,10 @@ const MAX_SETTING_STRING_LENGTH = 10 # Max length for string settings like lang
|
||||
var settings: Dictionary = {}
|
||||
|
||||
var default_settings: Dictionary = {
|
||||
"master_volume": 0.50, "music_volume": 0.40, "sfx_volume": 0.50, "language": "en"
|
||||
"master_volume": 0.50,
|
||||
"music_volume": 0.40,
|
||||
"sfx_volume": 0.50,
|
||||
"language": "en"
|
||||
}
|
||||
|
||||
var languages_data: Dictionary = {}
|
||||
@@ -32,7 +35,9 @@ func load_settings() -> void:
|
||||
|
||||
if load_result == OK:
|
||||
for key in default_settings.keys():
|
||||
var loaded_value = 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
|
||||
@@ -45,10 +50,15 @@ func load_settings() -> void:
|
||||
"SettingsManager"
|
||||
)
|
||||
settings[key] = default_settings[key]
|
||||
DebugManager.log_info("Settings loaded: " + str(settings), "SettingsManager")
|
||||
DebugManager.log_info(
|
||||
"Settings loaded: " + str(settings), "SettingsManager"
|
||||
)
|
||||
else:
|
||||
DebugManager.log_warn(
|
||||
"No settings file found (Error code: %d), using defaults" % load_result,
|
||||
(
|
||||
"No settings file found (Error code: %d), using defaults"
|
||||
% load_result
|
||||
),
|
||||
"SettingsManager"
|
||||
)
|
||||
settings = default_settings.duplicate()
|
||||
@@ -58,7 +68,9 @@ func load_settings() -> void:
|
||||
|
||||
|
||||
func _apply_all_settings():
|
||||
DebugManager.log_info("Applying settings: " + str(settings), "SettingsManager")
|
||||
DebugManager.log_info(
|
||||
"Applying settings: " + str(settings), "SettingsManager"
|
||||
)
|
||||
|
||||
# Apply language setting
|
||||
if "language" in settings:
|
||||
@@ -70,24 +82,33 @@ func _apply_all_settings():
|
||||
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"]))
|
||||
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"
|
||||
"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"]))
|
||||
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"
|
||||
"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"]))
|
||||
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"
|
||||
"SFX audio bus not found or sfx_volume setting missing",
|
||||
"SettingsManager"
|
||||
)
|
||||
|
||||
|
||||
@@ -99,7 +120,8 @@ func save_settings():
|
||||
var save_result = config.save(SETTINGS_FILE)
|
||||
if save_result != OK:
|
||||
DebugManager.log_error(
|
||||
"Failed to save settings (Error code: %d)" % save_result, "SettingsManager"
|
||||
"Failed to save settings (Error code: %d)" % save_result,
|
||||
"SettingsManager"
|
||||
)
|
||||
return false
|
||||
|
||||
@@ -119,7 +141,8 @@ func set_setting(key: String, value) -> bool:
|
||||
# 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"
|
||||
"Invalid value for setting '%s': %s" % [key, str(value)],
|
||||
"SettingsManager"
|
||||
)
|
||||
return false
|
||||
|
||||
@@ -157,7 +180,8 @@ func _validate_volume_setting(key: String, value) -> bool:
|
||||
# Check for NaN and infinity
|
||||
if is_nan(float_value) or is_inf(float_value):
|
||||
DebugManager.log_warn(
|
||||
"Invalid float value for %s: %s" % [key, str(value)], "SettingsManager"
|
||||
"Invalid float value for %s: %s" % [key, str(value)],
|
||||
"SettingsManager"
|
||||
)
|
||||
return false
|
||||
# Range validation
|
||||
@@ -170,7 +194,8 @@ func _validate_language_setting(value) -> bool:
|
||||
# Prevent extremely long strings
|
||||
if value.length() > MAX_SETTING_STRING_LENGTH:
|
||||
DebugManager.log_warn(
|
||||
"Language code too long: %d characters" % value.length(), "SettingsManager"
|
||||
"Language code too long: %d characters" % value.length(),
|
||||
"SettingsManager"
|
||||
)
|
||||
return false
|
||||
# Check for valid characters (alphanumeric and common separators only)
|
||||
@@ -178,11 +203,15 @@ func _validate_language_setting(value) -> bool:
|
||||
regex.compile("^[a-zA-Z0-9_-]+$")
|
||||
if not regex.search(value):
|
||||
DebugManager.log_warn(
|
||||
"Language code contains invalid characters: %s" % value, "SettingsManager"
|
||||
"Language code contains invalid characters: %s" % value,
|
||||
"SettingsManager"
|
||||
)
|
||||
return false
|
||||
# Check if language is supported
|
||||
if languages_data.has("languages") and languages_data.languages is Dictionary:
|
||||
if (
|
||||
languages_data.has("languages")
|
||||
and languages_data.languages is Dictionary
|
||||
):
|
||||
return value in languages_data.languages
|
||||
# Fallback to basic validation if languages not loaded
|
||||
return value in ["en", "ru"]
|
||||
@@ -192,7 +221,9 @@ func _validate_default_setting(key: String, value) -> bool:
|
||||
# Default validation: accept if type matches default setting type
|
||||
var default_value = default_settings.get(key)
|
||||
if default_value == null:
|
||||
DebugManager.log_warn("Unknown setting key in validation: %s" % key, "SettingsManager")
|
||||
DebugManager.log_warn(
|
||||
"Unknown setting key in validation: %s" % key, "SettingsManager"
|
||||
)
|
||||
return false
|
||||
return typeof(value) == typeof(default_value)
|
||||
|
||||
@@ -210,7 +241,9 @@ func _apply_setting_side_effect(key: String, value) -> void:
|
||||
AudioManager.update_music_volume(value)
|
||||
"sfx_volume":
|
||||
if AudioServer.get_bus_index("SFX") >= 0:
|
||||
AudioServer.set_bus_volume_db(AudioServer.get_bus_index("SFX"), linear_to_db(value))
|
||||
AudioServer.set_bus_volume_db(
|
||||
AudioServer.get_bus_index("SFX"), linear_to_db(value)
|
||||
)
|
||||
|
||||
|
||||
func load_languages():
|
||||
@@ -230,7 +263,11 @@ func load_languages():
|
||||
|
||||
languages_data = parsed_data
|
||||
DebugManager.log_info(
|
||||
"Languages loaded successfully: " + str(languages_data.languages.keys()), "SettingsManager"
|
||||
(
|
||||
"Languages loaded successfully: "
|
||||
+ str(languages_data.languages.keys())
|
||||
),
|
||||
"SettingsManager"
|
||||
)
|
||||
|
||||
|
||||
@@ -239,7 +276,8 @@ func _load_languages_file() -> String:
|
||||
if not file:
|
||||
var error_code = FileAccess.get_open_error()
|
||||
DebugManager.log_error(
|
||||
"Could not open languages.json (Error code: %d)" % error_code, "SettingsManager"
|
||||
"Could not open languages.json (Error code: %d)" % error_code,
|
||||
"SettingsManager"
|
||||
)
|
||||
return ""
|
||||
|
||||
@@ -247,14 +285,19 @@ func _load_languages_file() -> String:
|
||||
var file_size = file.get_length()
|
||||
if file_size > MAX_JSON_FILE_SIZE:
|
||||
DebugManager.log_error(
|
||||
"Languages.json file too large: %d bytes (max %d)" % [file_size, MAX_JSON_FILE_SIZE],
|
||||
(
|
||||
"Languages.json file too large: %d bytes (max %d)"
|
||||
% [file_size, MAX_JSON_FILE_SIZE]
|
||||
),
|
||||
"SettingsManager"
|
||||
)
|
||||
file.close()
|
||||
return ""
|
||||
|
||||
if file_size == 0:
|
||||
DebugManager.log_error("Languages.json file is empty", "SettingsManager")
|
||||
DebugManager.log_error(
|
||||
"Languages.json file is empty", "SettingsManager"
|
||||
)
|
||||
file.close()
|
||||
return ""
|
||||
|
||||
@@ -264,7 +307,8 @@ func _load_languages_file() -> String:
|
||||
|
||||
if file_error != OK:
|
||||
DebugManager.log_error(
|
||||
"Error reading languages.json (Error code: %d)" % file_error, "SettingsManager"
|
||||
"Error reading languages.json (Error code: %d)" % file_error,
|
||||
"SettingsManager"
|
||||
)
|
||||
return ""
|
||||
|
||||
@@ -274,27 +318,36 @@ func _load_languages_file() -> String:
|
||||
func _parse_languages_json(json_string: String) -> Dictionary:
|
||||
# Validate the JSON string is not empty
|
||||
if json_string.is_empty():
|
||||
DebugManager.log_error("Languages.json contains empty content", "SettingsManager")
|
||||
DebugManager.log_error(
|
||||
"Languages.json contains empty content", "SettingsManager"
|
||||
)
|
||||
return {}
|
||||
|
||||
var json = JSON.new()
|
||||
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],
|
||||
(
|
||||
"JSON parsing failed at line %d: %s"
|
||||
% [json.error_line, json.error_string]
|
||||
),
|
||||
"SettingsManager"
|
||||
)
|
||||
return {}
|
||||
|
||||
if not json.data or not json.data is Dictionary:
|
||||
DebugManager.log_error("Invalid JSON data structure in languages.json", "SettingsManager")
|
||||
DebugManager.log_error(
|
||||
"Invalid JSON data structure in languages.json", "SettingsManager"
|
||||
)
|
||||
return {}
|
||||
|
||||
return json.data
|
||||
|
||||
|
||||
func _load_default_languages_with_fallback(reason: String):
|
||||
DebugManager.log_warn("Loading default languages due to: " + reason, "SettingsManager")
|
||||
DebugManager.log_warn(
|
||||
"Loading default languages due to: " + reason, "SettingsManager"
|
||||
)
|
||||
_load_default_languages()
|
||||
|
||||
|
||||
@@ -302,9 +355,14 @@ func _load_default_languages():
|
||||
# Fallback language data when JSON file fails to load
|
||||
languages_data = {
|
||||
"languages":
|
||||
{"en": {"name": "English", "flag": "🇺🇸"}, "ru": {"name": "Русский", "flag": "🇷🇺"}}
|
||||
{
|
||||
"en": {"name": "English", "flag": "🇺🇸"},
|
||||
"ru": {"name": "Русский", "flag": "🇷🇺"}
|
||||
}
|
||||
}
|
||||
DebugManager.log_info("Default languages loaded as fallback", "SettingsManager")
|
||||
DebugManager.log_info(
|
||||
"Default languages loaded as fallback", "SettingsManager"
|
||||
)
|
||||
|
||||
|
||||
func get_languages_data():
|
||||
@@ -312,15 +370,21 @@ func get_languages_data():
|
||||
|
||||
|
||||
func reset_settings_to_defaults() -> void:
|
||||
DebugManager.log_info("Resetting all settings to defaults", "SettingsManager")
|
||||
DebugManager.log_info(
|
||||
"Resetting all settings to defaults", "SettingsManager"
|
||||
)
|
||||
for key in default_settings.keys():
|
||||
settings[key] = default_settings[key]
|
||||
_apply_setting_side_effect(key, settings[key])
|
||||
var save_success = save_settings()
|
||||
if save_success:
|
||||
DebugManager.log_info("Settings reset completed successfully", "SettingsManager")
|
||||
DebugManager.log_info(
|
||||
"Settings reset completed successfully", "SettingsManager"
|
||||
)
|
||||
else:
|
||||
DebugManager.log_error("Failed to save reset settings", "SettingsManager")
|
||||
DebugManager.log_error(
|
||||
"Failed to save reset settings", "SettingsManager"
|
||||
)
|
||||
|
||||
|
||||
func _validate_languages_structure(data: Dictionary) -> bool:
|
||||
@@ -344,16 +408,22 @@ func _validate_languages_structure(data: Dictionary) -> bool:
|
||||
func _validate_languages_root_structure(data: Dictionary) -> bool:
|
||||
"""Validate the root structure of languages data"""
|
||||
if not data.has("languages"):
|
||||
DebugManager.log_error("Languages.json missing 'languages' key", "SettingsManager")
|
||||
DebugManager.log_error(
|
||||
"Languages.json missing 'languages' key", "SettingsManager"
|
||||
)
|
||||
return false
|
||||
|
||||
var languages = data["languages"]
|
||||
if not languages is Dictionary:
|
||||
DebugManager.log_error("'languages' is not a dictionary", "SettingsManager")
|
||||
DebugManager.log_error(
|
||||
"'languages' is not a dictionary", "SettingsManager"
|
||||
)
|
||||
return false
|
||||
|
||||
if languages.is_empty():
|
||||
DebugManager.log_error("Languages dictionary is empty", "SettingsManager")
|
||||
DebugManager.log_error(
|
||||
"Languages dictionary is empty", "SettingsManager"
|
||||
)
|
||||
return false
|
||||
|
||||
return true
|
||||
@@ -367,28 +437,35 @@ func _validate_individual_languages(languages: Dictionary) -> bool:
|
||||
return true
|
||||
|
||||
|
||||
func _validate_single_language_entry(lang_code: Variant, lang_data: Variant) -> bool:
|
||||
func _validate_single_language_entry(
|
||||
lang_code: Variant, lang_data: Variant
|
||||
) -> bool:
|
||||
"""Validate a single language entry"""
|
||||
if not lang_code is String:
|
||||
DebugManager.log_error(
|
||||
"Language code is not a string: %s" % str(lang_code), "SettingsManager"
|
||||
"Language code is not a string: %s" % str(lang_code),
|
||||
"SettingsManager"
|
||||
)
|
||||
return false
|
||||
|
||||
if lang_code.length() > MAX_SETTING_STRING_LENGTH:
|
||||
DebugManager.log_error("Language code too long: %s" % lang_code, "SettingsManager")
|
||||
DebugManager.log_error(
|
||||
"Language code too long: %s" % lang_code, "SettingsManager"
|
||||
)
|
||||
return false
|
||||
|
||||
if not lang_data is Dictionary:
|
||||
DebugManager.log_error(
|
||||
"Language data for '%s' is not a dictionary" % lang_code, "SettingsManager"
|
||||
"Language data for '%s' is not a dictionary" % lang_code,
|
||||
"SettingsManager"
|
||||
)
|
||||
return false
|
||||
|
||||
# Validate required fields in language data
|
||||
if not lang_data.has("name") or not lang_data["name"] is String:
|
||||
DebugManager.log_error(
|
||||
"Language '%s' missing valid 'name' field" % lang_code, "SettingsManager"
|
||||
"Language '%s' missing valid 'name' field" % lang_code,
|
||||
"SettingsManager"
|
||||
)
|
||||
return false
|
||||
|
||||
|
||||
Reference in New Issue
Block a user