extends Minigame const FINAL_BOSS_GAME_DURATION: int = 20 * 60 const FINAL_BOSS_TOTAL_WORDS: int = 180 @export var game_duration: int = 4 * 60 @export var minimum_correct_ratio: float = 0.8 @export var winning_color: Color = Color.WHITE @export var max_words_count: int = 15 var sun_tween: Tween var words_to_present: Array[String] = [] var words_to_present_next: Array[String] = [] var progress_gauge_max_margin: float = 0.95 var total_number_of_words: int = 30 var tutorial_count: int = 0 var _boss_session_index: int = -1 var _boss_answer_start_ms: int = 0 var default_label_settings: LabelSettings var default_label_background_color: Color @onready var text_start_zone: Control = %ControlText @onready var texture_button_bin: TextureButton = $GameRoot/TextureButtonBin @onready var texture_button_book: TextureButton = $GameRoot/TextureButtonBook @onready var path_follow: PathFollow2D = %PathFollow2D @onready var texture_rect_text_box: TextureRect = %TextureRectTextBox @onready var label: Label = %Label @onready var progress_gauge: PercentMarginContainer = %ProgressionGaugePercentMarginContainer @onready var progress_gauge_goal: PercentMarginContainer = %ProgressionGaugeGoalPercentMarginContainer2 @onready var progress_gauge_internal: NinePatchRect = %ProgressionGaugeInternal @onready var adult_block: AdultBlock = %AdultBlock @onready var kalulu_boss: KaluluBoss = %KaluluBoss @onready var wrong_fx: WrongFX = %WrongFX @onready var right_stars: RightStarsFX = $GameRoot/Right_Stars func _ready() -> void: super() if adult_block and adult_block.has_signal("unlocked"): adult_block.unlocked.connect(_on_adult_block_unlocked) text_start_zone.set_drag_forwarding(_text_get_drag_data, Callable(), Callable()) (texture_button_bin as Control).set_drag_forwarding(Callable(), _button_can_drop_data, _button_bin_drop_data) (texture_button_book as Control).set_drag_forwarding(Callable(), _button_can_drop_data, _button_book_drop_data) minigame_ui.progression_container.hide() minigame_ui.progression_gauge.hide() label.hide() @warning_ignore("UNSAFE_PROPERTY_ACCESS") progress_gauge_max_margin = progress_gauge.margin_top_ratio default_label_settings = label.label_settings default_label_background_color = texture_rect_text_box.self_modulate # Skips the whole tutorial if UserDataManager.is_speech_played(Type.keys()[minigame_name] as String): tutorial_count = 2 func _text_get_drag_data(_at_position: Vector2) -> Variant: var duplicated: Control = text_start_zone.duplicate() duplicated.set_scale(Vector2(0.6, 0.6)) duplicated.set_modulate(Color(1, 1, 1, 0.4)) set_drag_preview(duplicated) return true func _find_stimuli_and_distractions() -> void: var data_array: Array[Dictionary] = Database.get_pseudowords_for_lesson(lesson_nb) if data_array.size() <= 0: Log.error("BossMinigame: Cannot start boss minigame since data is empty for lesson %d" % lesson_nb) return data_array.shuffle() if is_final_boss: max_words_count = data_array.size() words_to_present.clear() words_to_present_next.clear() for data: Dictionary in data_array: stimuli.append(data.Word) distractions.append(data.Pseudoword) if stimuli.size() >= max_words_count: break for word: String in stimuli: words_to_present.append(word) for word: String in distractions: words_to_present.append(word) words_to_present.shuffle() if tutorial_count == 0: words_to_present.erase(stimuli[0]) words_to_present.erase(distractions[0]) words_to_present.insert(0, distractions[0]) words_to_present.insert(0, stimuli[0]) total_number_of_words = words_to_present.size() if is_final_boss: game_duration = FINAL_BOSS_GAME_DURATION total_number_of_words = FINAL_BOSS_TOTAL_WORDS _ensure_words_to_present_count(FINAL_BOSS_TOTAL_WORDS) progress_gauge_goal.margin_top_ratio = (1. - minimum_correct_ratio) * progress_gauge_max_margin func _start() -> void: super() if _is_boss_session() and _boss_session_index == -1: _boss_session_index = UserDataManager.start_boss_session(int(Time.get_unix_time_from_system())) sun_tween = create_tween() sun_tween.tween_property(path_follow, "progress_ratio", 1, game_duration) sun_tween.finished.connect(_on_time_out) _present_next_word() func _present_next_word() -> void: if words_to_present.is_empty(): if words_to_present_next.is_empty(): _win() return words_to_present = words_to_present_next words_to_present.shuffle() words_to_present_next = [] label.show() label.text = words_to_present[0] if _is_boss_session(): _boss_answer_start_ms = Time.get_ticks_msec() if tutorial_count == 0: var speech: AudioStreamMP3 = Database.load_external_sound(Database.get_kalulu_speech_path(Type.keys()[minigame_name] as String, "intro_test_game_first_word")) minigame_ui.play_kalulu_speech(speech) await minigame_ui.kalulu_speech_ended func _on_time_out() -> void: if _get_win_ratio() >= minimum_correct_ratio: _win() else: _lose() func _button_can_drop_data(_at_position: Vector2, data: Variant) -> bool: return data func _button_bin_drop_data(_at_position: Vector2, _data: Variant) -> void: _on_answer_dropped(false) func _button_book_drop_data(_at_position: Vector2, _data: Variant) -> void: _on_answer_dropped(true) func _on_texture_button_bin_pressed() -> void: _on_answer_dropped(false) func _on_texture_button_book_pressed() -> void: _on_answer_dropped(true) func _on_answer_dropped(is_answered_real: bool) -> void: if words_to_present.size() < 1: Log.error("BossMinigame: Cannot process answer because there is no words to present") return texture_button_bin.set_disabled(true) texture_button_book.set_disabled(true) var is_really_real: bool = words_to_present[0] in stimuli var is_correct: bool = is_answered_real == is_really_real var response_log: Dictionary = {"word": words_to_present[0], "is_real_answer": is_answered_real} var awaited_response: Dictionary = {"word": words_to_present[0], "is_real_answer": is_really_real} _log_new_response(response_log, awaited_response) if _is_boss_session() and _boss_session_index >= 0: var response_time_ms: int = max(0, Time.get_ticks_msec() - _boss_answer_start_ms) UserDataManager.record_boss_answer( _boss_session_index, is_really_real, words_to_present[0].length(), response_time_ms, is_correct ) if is_correct: label.label_settings = label.label_settings.duplicate() label.label_settings.font_color = Minigame.LABEL_COLOR_NEUTRAL texture_rect_text_box.self_modulate = Minigame.LABEL_COLOR_WIN if is_answered_real: right_stars.global_position = texture_button_book.global_position + texture_button_book.get_size() / 2 right_stars.replay() else: right_stars.global_position = texture_button_bin.global_position + texture_button_bin.get_size() / 2 right_stars.replay() words_to_present.pop_front() if tutorial_count == 0: var speech: AudioStreamMP3 = Database.load_external_sound(Database.get_kalulu_speech_path(Type.keys()[minigame_name] as String, "win_test_game_first_word")) minigame_ui.play_kalulu_speech(speech) await minigame_ui.kalulu_speech_ended tutorial_count += 1 elif tutorial_count == 1: var speech: AudioStreamMP3 = Database.load_external_sound(Database.get_kalulu_speech_path(Type.keys()[minigame_name] as String, "win_test_game_second_word")) minigame_ui.play_kalulu_speech(speech) await minigame_ui.kalulu_speech_ended tutorial_count += 1 else: if is_answered_real: wrong_fx.global_position = texture_button_book.global_position + texture_button_book.get_size() / 2 wrong_fx.play() else: wrong_fx.global_position = texture_button_bin.global_position + texture_button_bin.get_size() / 2 wrong_fx.play() words_to_present_next.append(words_to_present.pop_front()) if tutorial_count == 0: var speech: AudioStreamMP3 = Database.load_external_sound(Database.get_kalulu_speech_path(Type.keys()[minigame_name] as String, "lose_test_game_first_word")) minigame_ui.play_kalulu_speech(speech) await minigame_ui.kalulu_speech_ended tutorial_count += 1 elif tutorial_count == 1: var speech: AudioStreamMP3 = Database.load_external_sound(Database.get_kalulu_speech_path(Type.keys()[minigame_name] as String, "lose_test_game_second_word")) minigame_ui.play_kalulu_speech(speech) await minigame_ui.kalulu_speech_ended tutorial_count += 1 await get_tree().create_timer(1).timeout label.label_settings = default_label_settings texture_rect_text_box.self_modulate = default_label_background_color texture_button_bin.set_disabled(false) texture_button_book.set_disabled(false) _update_progression_gauge() _present_next_word() func _update_progression_gauge() -> void: progress_gauge.margin_top_ratio = progress_gauge_max_margin - progress_gauge_max_margin / total_number_of_words * (total_number_of_words - words_to_present.size() - words_to_present_next.size()) if _get_win_ratio() >= minimum_correct_ratio: progress_gauge_internal.modulate = winning_color func _get_win_ratio() -> float: return 1. - float(words_to_present.size() + words_to_present_next.size()) / total_number_of_words func _ensure_words_to_present_count(target_count: int) -> void: if words_to_present.is_empty(): return var base_pool: Array[String] = words_to_present.duplicate() while words_to_present.size() < target_count: base_pool.shuffle() words_to_present.append_array(base_pool) if words_to_present.size() > target_count: words_to_present.resize(target_count) func show_adult_block() -> void: if adult_block and adult_block.has_method("show_block"): adult_block.call("show_block") func _on_adult_block_unlocked() -> void: await _reset() func _win() -> void: if _is_boss_session() and _boss_session_index >= 0: UserDataManager.finish_boss_session(_boss_session_index, true) await kalulu_boss.happy() await super() func _lose() -> void: if _is_boss_session() and _boss_session_index >= 0: UserDataManager.finish_boss_session(_boss_session_index, false) await kalulu_boss.sad() await super() func _is_boss_session() -> bool: return is_final_boss or gardens_data.has("boss_gate_lesson")