class_name PlayerObject extends RefCounted ## A player in the quiz: name, score and character. The players are kept in ## [code]Settings.player_array[/code]. signal first_point_triggered signal match_point_triggered signal winner_triggered signal point_given_sound signal player_point_given signal player_point_taken ## Picture shown until the player picks a character. const NO_CHARACTER_PATH: String = "res://noCharacter.png" @export var player_name: String @export var player_score: int ## Unique id that never changes, unlike the player's position in Settings.player_array. var id: String = _generate_uuid() var is_first_point: bool = true var character: Texture = load(NO_CHARACTER_PATH) ## The name of the picked character, its picture's file name, or empty when none is ## picked. Saved with the quiz, so the picture can be found again after a restart. var character_name: String = "" func _init(new_name: String) -> void: player_name = new_name player_score = 0 ## Shows [param texture], the character called [param new_name], for the player. func set_character(texture: Texture, new_name: String) -> void: character = texture character_name = new_name ## Goes back to no character, e.g. when the characters come from somewhere else. func clear_character() -> void: character = load(NO_CHARACTER_PATH) character_name = "" ## The player as a Dictionary that can be written as JSON, for [QuizSave]. func to_dict() -> Dictionary: return { "id": id, "name": player_name, "score": player_score, "is_first_point": is_first_point, "character": character_name, } ## A player made from [param data], as given by [method to_dict], or null when ## [param data] is not a player. The character's picture is not loaded here. static func from_dict(data: Variant) -> PlayerObject: if !(data is Dictionary) || !(data.get("name") is String) || !(data.get("id") is String): return null var player: PlayerObject = PlayerObject.new(data.name) player.id = data.id player.player_score = maxi(int(data.get("score", 0)), 0) player.is_first_point = bool(data.get("is_first_point", player.player_score == 0)) player.character_name = str(data.get("character", "")) return player ## Returns a random version 4 UUID, e.g. "3f2b8c1e-9a4d-4e7f-b2c6-5d8e1f0a7b93". static func _generate_uuid() -> String: var bytes: PackedByteArray = Crypto.new().generate_random_bytes(16) bytes[6] = (bytes[6] & 0x0f) | 0x40 bytes[8] = (bytes[8] & 0x3f) | 0x80 var hex: String = bytes.hex_encode() return "%s-%s-%s-%s-%s" % [hex.substr(0, 8), hex.substr(8, 4), hex.substr(12, 4), hex.substr(16, 4), hex.substr(20, 12)] ## Gives the player a point. With a winning score, the point that reaches it wins; a ## prepared quiz has none, so points just add up until every song has played. func add_point() -> void: var has_winning_score: bool = !Settings.is_prepared_quiz() if has_winning_score && player_score >= Settings.winning_score: return player_score += 1 player_point_given.emit() var first_point: bool = player_score == 1 && is_first_point if player_score == 1: is_first_point = false # With a low winning score the first point can also be match point or the win, # so the more important event is checked first if has_winning_score && player_score == Settings.winning_score: winner_triggered.emit() elif has_winning_score && player_score == Settings.winning_score - 1: match_point_triggered.emit() elif first_point: first_point_triggered.emit() else: point_given_sound.emit() func minus_point() -> void: if player_score > 0: player_score -= 1 player_point_taken.emit() if player_score == 0: is_first_point = true func reset_points() -> void: player_score = 0 is_first_point = true