Settings has a Music folder row. A folder with subfolders is played as soundtracks: Next picks a random subfolder and a random song in it, the subfolder is the answer's soundtrack and the file name without its type the song. The inspiration list and Search show the subfolders. A folder with songs and no subfolders is a prepared quiz, played once in order (natural sort) or at random, showing only the song title. It has no winning score: after the last song Next becomes Show winner, and the players with the most points win, ties included. While a folder is used nothing but the characters is asked from the server: Sync, QR, Statistics, the update check and play counting are off. Characters fall back to the cached pictures when the server can't be reached. mp3, ogg and wav play; other files and broken ones are skipped. The old local playback code, the hidden Open button and the hidden Local toggle are removed. Next now waits for the song to start before preparing the next one.
161 lines
6.5 KiB
GDScript
161 lines
6.5 KiB
GDScript
class_name ScoreGraph
|
|
extends Control
|
|
## Draws every player's score after each song of the game as a step line, so streaks and
|
|
## stretches where nobody scored stand out. Shown on the Winner popup.
|
|
##
|
|
## Colours and sizes come from the theme's [code]ScoreGraph[/code] type. Each player gets
|
|
## the colour of their position in [code]Settings.player_array[/code].
|
|
|
|
const THEME_TYPE: StringName = &"ScoreGraph"
|
|
## Room for the score labels on the left and right, and the song labels below.
|
|
const MARGIN_LEFT: float = 32.0
|
|
const MARGIN_RIGHT: float = 32.0
|
|
const MARGIN_TOP: float = 22.0
|
|
const MARGIN_BOTTOM: float = 44.0
|
|
## About how many song numbers fit along the bottom.
|
|
const MAX_SONG_LABELS: int = 20
|
|
|
|
var _history: ScoreHistory
|
|
var _winning_score: int = 0
|
|
|
|
|
|
## Shows [param history]. The y axis goes up to [param winning_score], or higher if
|
|
## someone has more points. A winning score of 0 draws no winning line.
|
|
func show_history(history: ScoreHistory, winning_score: int) -> void:
|
|
_history = history
|
|
_winning_score = winning_score
|
|
queue_redraw()
|
|
|
|
|
|
## The colour of the player at [param index] in the player list.
|
|
func player_color(index: int) -> Color:
|
|
return get_theme_color(StringName("player_%d" % (index % 7 + 1)), THEME_TYPE)
|
|
|
|
|
|
## Where the line of the player at [param index] is drawn after each song, starting
|
|
## before the first song.
|
|
func line_points(index: int) -> PackedVector2Array:
|
|
var points: PackedVector2Array = []
|
|
var scores: Array[int] = _history.scores_after_songs(_history.players[index].id)
|
|
points.append(_point(0, 0, index))
|
|
for song: int in range(1, scores.size()):
|
|
if scores[song] != scores[song - 1]:
|
|
points.append(_point(song, scores[song - 1], index))
|
|
points.append(_point(song, scores[song], index))
|
|
points.append(_point(scores.size() - 1, scores.back(), index))
|
|
return points
|
|
|
|
|
|
func _draw() -> void:
|
|
if _history == null:
|
|
return
|
|
var font: Font = get_theme_default_font()
|
|
var font_size: int = get_theme_font_size(&"font_size", THEME_TYPE)
|
|
var text_color: Color = get_theme_color(&"axis_text", THEME_TYPE)
|
|
_draw_score_axis(font, font_size, text_color)
|
|
_draw_song_axis(font, font_size, text_color)
|
|
|
|
var line_width: int = get_theme_constant(&"line_width", THEME_TYPE)
|
|
var dot_radius: int = get_theme_constant(&"dot_radius", THEME_TYPE)
|
|
for index: int in _history.players.size():
|
|
var color: Color = player_color(index)
|
|
var scores: Array[int] = _history.scores_after_songs(_history.players[index].id)
|
|
draw_polyline(line_points(index), color, line_width, true)
|
|
for song: int in range(1, scores.size()):
|
|
if scores[song] > scores[song - 1]:
|
|
draw_circle(_point(song, scores[song], index), dot_radius, color)
|
|
var label_positions: Array[Vector2] = score_label_positions()
|
|
for index: int in _history.players.size():
|
|
draw_string(font, label_positions[index],
|
|
str(_history.final_score(_history.players[index].id)),
|
|
HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, player_color(index))
|
|
|
|
|
|
## Where each player's score is written at the end of their line, as the baseline of the
|
|
## text. Scores that would overlap are moved apart.
|
|
func score_label_positions() -> Array[Vector2]:
|
|
var font_size: int = get_theme_font_size(&"font_size", THEME_TYPE)
|
|
var positions: Array[Vector2] = []
|
|
for index: int in _history.players.size():
|
|
var score: int = _history.final_score(_history.players[index].id)
|
|
var end: Vector2 = _point(_history.songs.size(), score, index)
|
|
positions.append(end + Vector2(8, font_size * 0.35))
|
|
# From the top down, push each label below the one above it
|
|
var order: Array = range(positions.size())
|
|
order.sort_custom(func(a: int, b: int) -> bool: return positions[a].y < positions[b].y)
|
|
for i: int in range(1, order.size()):
|
|
var above: Vector2 = positions[order[i - 1]]
|
|
if positions[order[i]].y - above.y < font_size:
|
|
positions[order[i]].y = above.y + font_size
|
|
return positions
|
|
|
|
|
|
## The songs whose numbers are written along the bottom.
|
|
func labelled_songs() -> Array[int]:
|
|
var song_count: int = _history.songs.size()
|
|
var step: int = maxi(1, ceili(song_count / float(MAX_SONG_LABELS)))
|
|
var songs: Array[int] = []
|
|
for song: int in range(1, song_count + 1):
|
|
# The last song is always labelled, unless that would crowd its neighbour
|
|
var is_last: bool = song == song_count && (song - 1) % step >= step / 2.0
|
|
if (song - 1) % step == 0 || is_last:
|
|
songs.append(song)
|
|
return songs
|
|
|
|
|
|
## Where on the x axis [param song] is, counting the start of the game as song 0.
|
|
func song_x(song: int) -> float:
|
|
return _x(song)
|
|
|
|
|
|
func _draw_score_axis(font: Font, font_size: int, text_color: Color) -> void:
|
|
var grid_color: Color = get_theme_color(&"grid", THEME_TYPE)
|
|
var top: int = _top_score()
|
|
var step: int = maxi(1, ceili(top / 10.0))
|
|
for score: int in range(0, top + 1, step):
|
|
var y: float = _y(score)
|
|
draw_line(Vector2(MARGIN_LEFT, y), Vector2(size.x - MARGIN_RIGHT, y), grid_color, 1)
|
|
draw_string(font, Vector2(0, y + font_size * 0.35), str(score),
|
|
HORIZONTAL_ALIGNMENT_RIGHT, MARGIN_LEFT - 8, font_size, text_color)
|
|
if _winning_score <= 0:
|
|
return
|
|
var winning_color: Color = get_theme_color(&"winning_line", THEME_TYPE)
|
|
var winning_y: float = _y(_winning_score)
|
|
draw_dashed_line(Vector2(MARGIN_LEFT, winning_y), Vector2(size.x - MARGIN_RIGHT, winning_y),
|
|
winning_color, 1, 6)
|
|
draw_string(font, Vector2(MARGIN_LEFT + 4, winning_y - 6), "winning score",
|
|
HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, winning_color)
|
|
|
|
|
|
func _draw_song_axis(font: Font, font_size: int, text_color: Color) -> void:
|
|
var label_y: float = size.y - MARGIN_BOTTOM + font_size + 6
|
|
for song: int in labelled_songs():
|
|
draw_string(font, Vector2(_x(song) - 20, label_y), str(song),
|
|
HORIZONTAL_ALIGNMENT_CENTER, 40, font_size, text_color)
|
|
draw_string(font, Vector2(0, size.y - 4), "Song", HORIZONTAL_ALIGNMENT_CENTER, size.x,
|
|
font_size, text_color)
|
|
|
|
|
|
func _point(song: int, score: int, index: int) -> Vector2:
|
|
# Lines of players with the same score sit side by side instead of on top of each other
|
|
var spread: float = get_theme_constant(&"line_width", THEME_TYPE) \
|
|
* (index - (_history.players.size() - 1) / 2.0)
|
|
return Vector2(_x(song), _y(score) + spread)
|
|
|
|
|
|
func _x(song: int) -> float:
|
|
var width: float = size.x - MARGIN_LEFT - MARGIN_RIGHT
|
|
return MARGIN_LEFT + song * width / maxi(_history.songs.size(), 1)
|
|
|
|
|
|
func _y(score: int) -> float:
|
|
var height: float = size.y - MARGIN_TOP - MARGIN_BOTTOM
|
|
return size.y - MARGIN_BOTTOM - score * height / _top_score()
|
|
|
|
|
|
func _top_score() -> int:
|
|
var top: int = maxi(_winning_score, 1)
|
|
for player: PlayerObject in _history.players:
|
|
top = maxi(top, _history.final_score(player.id))
|
|
return top
|