diff --git a/resources/user/student_progression.gd b/resources/user/student_progression.gd index 64b4bf26..67736be5 100644 --- a/resources/user/student_progression.gd +++ b/resources/user/student_progression.gd @@ -55,6 +55,25 @@ static func _make_zero_durations(minigame_count: int) -> PackedInt32Array: return durations +# Resizes a games status array to target_size, keeping the existing statuses for +# the slots that remain (trim surplus / pad new slots with LOCKED). Used when a +# lesson's minigame count changes so old saves don't lose progress on resize. +static func _resize_games_array(games: Array, target_size: int) -> Array: + var resized: Array = [] + for index: int in range(target_size): + resized.append(games[index] if index < games.size() else Status.LOCKED) + return resized + + +# Same idea for the duration metrics: keep recorded times for remaining slots. +static func _resize_durations(durations: PackedInt32Array, target_size: int) -> PackedInt32Array: + var resized: PackedInt32Array = PackedInt32Array() + resized.resize(target_size) + for index: int in range(mini(target_size, durations.size())): + resized[index] = durations[index] + return resized + + # Make sure the unlocks are correct func init_unlocks() -> void: if not unlocks: @@ -111,17 +130,24 @@ func ensure_data_integrity(data: Dictionary[int, Dictionary]) -> Dictionary: if not garden.has("total_duration"): garden["total_duration"] = _make_zero_durations(minigame_count) - # Check array "games" - if typeof(garden["games"]) != TYPE_ARRAY or (garden["games"] as Array).size() != minigame_count: + # Check array "games": a corrupt (non-array) value is reset, but a size + # mismatch (the lesson's minigame count changed) is resized in place so we + # keep existing progress for the slots that remain instead of wiping it. + if typeof(garden["games"]) != TYPE_ARRAY: if not is_init: Log.warn("StudentProgression: Garden %d: invalid format for 'games' → reset." % index) garden["games"] = _make_locked_games_array(minigame_count) + elif (garden["games"] as Array).size() != minigame_count: + if not is_init: + Log.warn("StudentProgression: Garden %d: 'games' resized from %d to %d, progress preserved." % [index, (garden["games"] as Array).size(), minigame_count]) + garden["games"] = _resize_games_array(garden["games"] as Array, minigame_count) - # Make sure duration arrays match the minigame count + # Keep duration metrics aligned with the minigame count, preserving the + # recorded times for the slots that remain. if (garden["last_duration"] as PackedInt32Array).size() != minigame_count: - garden["last_duration"] = _make_zero_durations(minigame_count) + garden["last_duration"] = _resize_durations(garden["last_duration"] as PackedInt32Array, minigame_count) if (garden["total_duration"] as PackedInt32Array).size() != minigame_count: - garden["total_duration"] = _make_zero_durations(minigame_count) + garden["total_duration"] = _resize_durations(garden["total_duration"] as PackedInt32Array, minigame_count) # Check value outside of possible enum values for game_index: int in range((garden["games"] as Array).size()): diff --git a/sources/menus/settings/lesson_unlock.gd b/sources/menus/settings/lesson_unlock.gd index a2d2da6d..82d8b02d 100644 --- a/sources/menus/settings/lesson_unlock.gd +++ b/sources/menus/settings/lesson_unlock.gd @@ -23,7 +23,7 @@ func _ready() -> void: exercise_option_button_1.add_item(tr(status)) exercise_option_button_2.add_item(tr(status)) exercise_option_button_3.add_item(tr(status)) - + reload() @@ -45,77 +45,76 @@ func reload() -> void: func _set_lesson_number(value: int) -> void: lesson_number = value - + if not lesson_label: return - + lesson_label.text = str(lesson_number) - + look_and_learn_option_button.select(unlocks[lesson_number]["look_and_learn"] as int) - exercise_option_button_1.select(unlocks[lesson_number]["games"][0] as int) - exercise_option_button_2.select(unlocks[lesson_number]["games"][1] as int) - exercise_option_button_3.select(unlocks[lesson_number]["games"][2] as int) + # A lesson can have 1–3 minigames, so only populate the buttons that map to a + # real game and disable the surplus ones (the grid keeps all three cells). + var games: Array = unlocks[lesson_number]["games"] + var exercise_buttons: Array[OptionButton] = [exercise_option_button_1, exercise_option_button_2, exercise_option_button_3] + for index: int in range(exercise_buttons.size()): + var button: OptionButton = exercise_buttons[index] + if index < games.size(): + button.disabled = false + button.select(games[index] as int) + else: + button.disabled = true + button.select(-1) func _set_lesson_gps(value: String) -> void: lesson_gps = value if not gps_label: return - + gps_label.text = value func _on_look_and_learn_option_button_item_selected(index: int) -> void: unlocks[lesson_number]["look_and_learn"] = index - + if index == StudentProgression.Status.LOCKED: if lesson_number == 1: unlocks[lesson_number]["look_and_learn"] = StudentProgression.Status.UNLOCKED else: unlocks[lesson_number - 1]["look_and_learn"] = StudentProgression.Status.UNLOCKED - unlocks[lesson_number - 1]["games"][0] = StudentProgression.Status.LOCKED - unlocks[lesson_number - 1]["games"][1] = StudentProgression.Status.LOCKED - unlocks[lesson_number - 1]["games"][2] = StudentProgression.Status.LOCKED - - unlocks[lesson_number]["games"][0] = StudentProgression.Status.LOCKED - unlocks[lesson_number]["games"][1] = StudentProgression.Status.LOCKED - unlocks[lesson_number]["games"][2] = StudentProgression.Status.LOCKED - + _set_lesson_games(lesson_number - 1, StudentProgression.Status.LOCKED) + + _set_lesson_games(lesson_number, StudentProgression.Status.LOCKED) + for lesson: int in unlocks.keys(): if lesson > lesson_number: unlocks[lesson]["look_and_learn"] = StudentProgression.Status.LOCKED - unlocks[lesson]["games"][0] = StudentProgression.Status.LOCKED - unlocks[lesson]["games"][1] = StudentProgression.Status.LOCKED - unlocks[lesson]["games"][2] = StudentProgression.Status.LOCKED - + _set_lesson_games(lesson, StudentProgression.Status.LOCKED) + elif index == StudentProgression.Status.UNLOCKED: - unlocks[lesson_number]["games"][0] = StudentProgression.Status.LOCKED - unlocks[lesson_number]["games"][1] = StudentProgression.Status.LOCKED - unlocks[lesson_number]["games"][2] = StudentProgression.Status.LOCKED + _set_lesson_games(lesson_number, StudentProgression.Status.LOCKED) for lesson: int in unlocks.keys(): if lesson < lesson_number: unlocks[lesson]["look_and_learn"] = StudentProgression.Status.COMPLETED - unlocks[lesson]["games"][0] = StudentProgression.Status.COMPLETED - unlocks[lesson]["games"][1] = StudentProgression.Status.COMPLETED - unlocks[lesson]["games"][2] = StudentProgression.Status.COMPLETED + _set_lesson_games(lesson, StudentProgression.Status.COMPLETED) elif lesson > lesson_number: unlocks[lesson]["look_and_learn"] = StudentProgression.Status.LOCKED - unlocks[lesson]["games"][0] = StudentProgression.Status.LOCKED - unlocks[lesson]["games"][1] = StudentProgression.Status.LOCKED - unlocks[lesson]["games"][2] = StudentProgression.Status.LOCKED + _set_lesson_games(lesson, StudentProgression.Status.LOCKED) elif index == StudentProgression.Status.COMPLETED: - unlocks[lesson_number]["games"][0] = StudentProgression.Status.UNLOCKED - unlocks[lesson_number]["games"][1] = StudentProgression.Status.UNLOCKED - unlocks[lesson_number]["games"][2] = StudentProgression.Status.UNLOCKED + _set_lesson_games(lesson_number, StudentProgression.Status.UNLOCKED) for lesson: int in unlocks.keys(): if lesson < lesson_number: unlocks[lesson]["look_and_learn"] = StudentProgression.Status.COMPLETED - unlocks[lesson]["games"][0] = StudentProgression.Status.COMPLETED - unlocks[lesson]["games"][1] = StudentProgression.Status.COMPLETED - unlocks[lesson]["games"][2] = StudentProgression.Status.COMPLETED + _set_lesson_games(lesson, StudentProgression.Status.COMPLETED) elif lesson > lesson_number: unlocks[lesson]["look_and_learn"] = StudentProgression.Status.LOCKED - unlocks[lesson]["games"][0] = StudentProgression.Status.LOCKED - unlocks[lesson]["games"][1] = StudentProgression.Status.LOCKED - unlocks[lesson]["games"][2] = StudentProgression.Status.LOCKED + _set_lesson_games(lesson, StudentProgression.Status.LOCKED) unlocks_changed.emit() + + +# Sets every minigame of a lesson to the same status, respecting the lesson's +# actual minigame count (1–3) rather than assuming a fixed three. +func _set_lesson_games(lesson: int, status: StudentProgression.Status) -> void: + var games: Array = unlocks[lesson]["games"] + for game_index: int in range(games.size()): + games[game_index] = status