class_name WinnerPopup extends BasePopup ## Shows who won, with their character, where the other players placed, and how the game ## went: a graph of everyone's score after each song and a few facts about it. ## ## The winners' pictures bounce in, confetti falls over the panel and the graph draws its ## lines from left to right. See [Anim]. ## How many pieces of confetti fly when someone wins. const CONFETTI_AMOUNT: int = 160 ## Size of the coloured square in front of each name in the legend. const LEGEND_SQUARE_SIZE: Vector2 = Vector2(14, 14) @onready var winner_panel: Panel = %WinnerPanel @onready var winner_picture: TextureRect = %WinnerPicture @onready var winner_label: Label = %WinnerLabel @onready var players_list: VBoxContainer = %PlayersList @onready var legend: HBoxContainer = %Legend @onready var score_graph: ScoreGraph = %ScoreGraph @onready var streak_label: Label = %StreakLabel @onready var standstill_label: Label = %StandstillLabel @onready var shared_label: Label = %SharedLabel func _ready() -> void: visible = false func show_winner(winner: PlayerObject) -> void: var winners: Array[PlayerObject] = [winner] show_winners(winners) ## Shows [param winners], who share the top score, with everyone else's placing below. ## Tied players get the same placing. With no players, it says the quiz is over. func show_winners(winners: Array[PlayerObject]) -> void: open() winner_label.text = winners_text(winners) _show_pictures(winners) for child: Node in players_list.get_children(): child.queue_free() var sorted_players: Array[PlayerObject] = Settings.player_array.duplicate() sorted_players.sort_custom( func(a: PlayerObject, b: PlayerObject) -> bool: return a.player_score > b.player_score ) for player: PlayerObject in sorted_players: if player in winners: continue var label: Label = Label.new() label.text = "%d. %s - %s" % [placing(player), player.player_name, points_text(player.player_score)] players_list.add_child(label) show_history(ScoreHistory.from_playlist(Settings.player_array)) _celebrate() ## The headline for [param winners], e.g. "Crono won with 5 points!!" or "Crono and ## Marle won with 5 points!!". static func winners_text(winners: Array[PlayerObject]) -> String: if winners.is_empty(): return "The quiz is over!" var names: PackedStringArray = [] for winner: PlayerObject in winners: names.append(winner.player_name) var joined: String = names[-1] if names.size() > 1: joined = ", ".join(names.slice(0, -1)) + " and " + names[-1] return "%s won with %s!!" % [joined, points_text(winners[0].player_score)] ## [param points] in words, e.g. "1 point" or "5 points". static func points_text(points: int) -> String: return "%d point%s" % [points, "" if points == 1 else "s"] ## Where [param player] placed: 1 plus how many players have more points, so tied ## players share a placing. static func placing(player: PlayerObject) -> int: var place: int = 1 for other: PlayerObject in Settings.player_array: if other.player_score > player.player_score: place += 1 return place # The winners' pictures bounce in, confetti flies and the graph draws itself func _celebrate() -> void: for picture: Node in winner_picture.get_parent().get_children(): if picture is TextureRect && (picture as TextureRect).visible: Anim.bounce_in(picture) score_graph.draw_lines_in() if Anim.enabled(): _throw_confetti() # A burst of confetti from the top of the panel, in the players' graph colours func _throw_confetti() -> void: var old: Node = winner_panel.get_node_or_null("Confetti") if old != null: old.free() var confetti: CPUParticles2D = CPUParticles2D.new() confetti.name = "Confetti" confetti.position = Vector2(winner_panel.size.x / 2, 0) confetti.emission_shape = CPUParticles2D.EMISSION_SHAPE_RECTANGLE confetti.emission_rect_extents = Vector2(winner_panel.size.x / 2, 4) confetti.amount = CONFETTI_AMOUNT confetti.lifetime = 2.6 confetti.one_shot = true confetti.explosiveness = 0.85 confetti.direction = Vector2.DOWN confetti.spread = 35.0 confetti.gravity = Vector2(0, 260) confetti.initial_velocity_min = 80.0 confetti.initial_velocity_max = 260.0 confetti.angular_velocity_min = -360.0 confetti.angular_velocity_max = 360.0 confetti.scale_amount_min = 5.0 confetti.scale_amount_max = 9.0 var colours: Gradient = Gradient.new() colours.interpolation_mode = Gradient.GRADIENT_INTERPOLATE_CONSTANT colours.offsets = PackedFloat32Array() colours.colors = PackedColorArray() for i: int in 7: colours.add_point(i / 7.0, score_graph.player_color(i)) confetti.color_initial_ramp = colours confetti.finished.connect(confetti.queue_free) winner_panel.add_child(confetti) confetti.emitting = true # One picture per winner, next to each other func _show_pictures(winners: Array[PlayerObject]) -> void: var header: Node = winner_picture.get_parent() for extra: Node in header.get_children(): if extra != winner_picture && extra is TextureRect: header.remove_child(extra) extra.queue_free() winner_picture.visible = !winners.is_empty() for index: int in winners.size(): var picture: TextureRect = winner_picture if index > 0: picture = winner_picture.duplicate() header.add_child(picture) header.move_child(picture, winner_picture.get_index() + index) _show_character(picture, winners[index]) func _show_character(picture: TextureRect, player: PlayerObject) -> void: picture.texture = player.character # The "no character" placeholder is pixel art, which blurs when scaled up smoothly if player.character.resource_path == PlayerObject.NO_CHARACTER_PATH: picture.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST else: picture.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR ## Shows how the game went: the score graph, its legend and the summary boxes. func show_history(history: ScoreHistory) -> void: # A prepared quiz has no winning score to draw var winning_score: int = 0 if Settings.is_prepared_quiz() else Settings.winning_score score_graph.show_history(history, winning_score) streak_label.text = history.longest_streak_text() standstill_label.text = history.longest_standstill_text() shared_label.text = history.most_shared_text() for child: Node in legend.get_children(): legend.remove_child(child) child.queue_free() for index: int in history.players.size(): var player: PlayerObject = history.players[index] var entry: HBoxContainer = HBoxContainer.new() var square: ColorRect = ColorRect.new() square.custom_minimum_size = LEGEND_SQUARE_SIZE square.size_flags_vertical = Control.SIZE_SHRINK_CENTER square.color = score_graph.player_color(index) entry.add_child(square) var name_label: Label = Label.new() name_label.text = "%s %d" % [player.player_name, history.final_score(player.id)] entry.add_child(name_label) legend.add_child(entry)